commit c956b94983fb32965eaf86454c9a8bafb4b526da Author: vl.arkhangelskii Date: Mon Sep 21 04:06:43 2026 +0300 feat(proj): init diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..27994b3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +.env +.git/ +.gitignore +__pycache__/ +*.py[cod] +*.db +data/ +context/ +.idea/ +.vscode/ +web/node_modules/ +web/dist/ +venv/ +.venv/ +src/**/bin/ +src/**/obj/ +api/ + diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..37bf2f8 --- /dev/null +++ b/.env.example @@ -0,0 +1,29 @@ +BOT_TOKEN=your_telegram_bot_token_here +TELEGRAM_BOT_USERNAME=YourBotUsernameWithoutAt + +# Recommended with docker-compose network_mode: host +PROXY_URL=socks5://127.0.0.1:10808 + +# Required for bot → API (POST /api/auth/internal) +API_TOKEN=change-me-to-a-long-random-string + +# Bot talks to the C# API (same HTTP contract as before) +API_BASE_URL=http://127.0.0.1:51291 + +# PostgreSQL (docker-compose service `db`) +POSTGRES_DB=please_pay_me +POSTGRES_USER=ppm +POSTGRES_PASSWORD=ppm + +# Optional explicit JWT secret (defaults to derived from BOT_TOKEN) +# JWT_SECRET=change-me-long-random + +CORS_ORIGINS=* + +# Yandex ID (authorization code; secret stays on the API) +YANDEX_CLIENT_ID= +YANDEX_CLIENT_SECRET= +YANDEX_REDIRECT_URI=https://please-pay-me.ru/ +PUBLIC_WEB_ORIGIN=https://please-pay-me.ru +# Optional extra callbacks, comma-separated +# YANDEX_REDIRECT_URIS=http://localhost:51290/,http://localhost:5173/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3dc724f --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.env +__pycache__/ +*.py[cod] +*.db +.venv/ +venv/ +.idea/ +.vscode/ +*.egg-info/ +dist/ +build/ +web/node_modules/ +web/dist/ +web/public/*.apk diff --git a/404.html b/404.html new file mode 100644 index 0000000..41eec4a --- /dev/null +++ b/404.html @@ -0,0 +1,261 @@ + + + + + + 404 — Дожить до ЗП + + + + + + +
+ + + + + +

404

+

Страница ушла куда-то

+

+ Такого адреса нет.

+ + + +

ошибка · страница не найдена

+
+ + diff --git a/502.html b/502.html new file mode 100644 index 0000000..c954825 --- /dev/null +++ b/502.html @@ -0,0 +1,262 @@ + + + + + + 502 — Дожить до ЗП + + + + + + +
+ + + + + +

502

+

Сервер не отвечает

+

+ Шлюз не смог достучаться до кабинета. Обычно это на минуту — обновите страницу. +

+ + + +

ошибка · плохой шлюз

+
+ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a1f86f3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 + +WORKDIR /app + +RUN groupadd --system --gid 1000 bot \ + && useradd --system --uid 1000 --gid bot --home-dir /app --shell /usr/sbin/nologin bot + +COPY requirements.txt . +RUN pip install -r requirements.txt + +COPY bot ./bot + +RUN chown -R bot:bot /app + +USER bot + +CMD ["python", "-m", "bot.main"] diff --git a/Dockerfile.api b/Dockerfile.api new file mode 100644 index 0000000..0dc385c --- /dev/null +++ b/Dockerfile.api @@ -0,0 +1,20 @@ +# syntax=docker/dockerfile:1 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src +COPY PleasePayMe.sln ./ +COPY src/PleasePayMe.Domain/PleasePayMe.Domain.csproj src/PleasePayMe.Domain/ +COPY src/PleasePayMe.Application/PleasePayMe.Application.csproj src/PleasePayMe.Application/ +COPY src/PleasePayMe.Infrastructure/PleasePayMe.Infrastructure.csproj src/PleasePayMe.Infrastructure/ +COPY src/PleasePayMe.Api/PleasePayMe.Api.csproj src/PleasePayMe.Api/ +RUN dotnet restore src/PleasePayMe.Api/PleasePayMe.Api.csproj +COPY src/ ./src/ +RUN dotnet publish src/PleasePayMe.Api/PleasePayMe.Api.csproj -c Release -o /app/publish /p:UseAppHost=false + +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final +WORKDIR /app +ENV ASPNETCORE_URLS=http://+:8000 +EXPOSE 8000 +COPY --from=build /app/publish . +USER $APP_UID +ENTRYPOINT ["dotnet", "PleasePayMe.Api.dll"] diff --git a/Dockerfile.web b/Dockerfile.web new file mode 100644 index 0000000..e54b683 --- /dev/null +++ b/Dockerfile.web @@ -0,0 +1,17 @@ +# syntax=docker/dockerfile:1 + +FROM node:22-alpine AS build +WORKDIR /web +COPY web/package.json web/package-lock.json* ./ +RUN npm install +COPY web/ ./ +ARG VITE_API_BASE_URL= +ARG VITE_TELEGRAM_BOT_USERNAME= +ENV VITE_API_BASE_URL=$VITE_API_BASE_URL \ + VITE_TELEGRAM_BOT_USERNAME=$VITE_TELEGRAM_BOT_USERNAME +RUN npm run build + +FROM nginx:1.27-alpine +COPY web/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /web/dist /usr/share/nginx/html +EXPOSE 80 diff --git a/PleasePayMe.sln b/PleasePayMe.sln new file mode 100644 index 0000000..8458bbf --- /dev/null +++ b/PleasePayMe.sln @@ -0,0 +1,99 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PleasePayMe.Domain", "src\PleasePayMe.Domain\PleasePayMe.Domain.csproj", "{C857E6E0-79E7-425A-89AA-2249B8E97C84}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PleasePayMe.Application", "src\PleasePayMe.Application\PleasePayMe.Application.csproj", "{E5B4B880-6D8F-4224-9134-E009FE1DD84F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PleasePayMe.Infrastructure", "src\PleasePayMe.Infrastructure\PleasePayMe.Infrastructure.csproj", "{0C1C947F-D99C-4287-B664-858604606AB4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PleasePayMe.Api", "src\PleasePayMe.Api\PleasePayMe.Api.csproj", "{2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PleasePayMe.Domain.Tests", "src\PleasePayMe.Domain.Tests\PleasePayMe.Domain.Tests.csproj", "{17EDC00D-3329-4839-8A49-86F8CD7D3A15}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Debug|x64.ActiveCfg = Debug|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Debug|x64.Build.0 = Debug|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Debug|x86.ActiveCfg = Debug|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Debug|x86.Build.0 = Debug|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Release|Any CPU.Build.0 = Release|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Release|x64.ActiveCfg = Release|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Release|x64.Build.0 = Release|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Release|x86.ActiveCfg = Release|Any CPU + {C857E6E0-79E7-425A-89AA-2249B8E97C84}.Release|x86.Build.0 = Release|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Debug|x64.ActiveCfg = Debug|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Debug|x64.Build.0 = Debug|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Debug|x86.ActiveCfg = Debug|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Debug|x86.Build.0 = Debug|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Release|Any CPU.Build.0 = Release|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Release|x64.ActiveCfg = Release|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Release|x64.Build.0 = Release|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Release|x86.ActiveCfg = Release|Any CPU + {E5B4B880-6D8F-4224-9134-E009FE1DD84F}.Release|x86.Build.0 = Release|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Debug|x64.Build.0 = Debug|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Debug|x86.Build.0 = Debug|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Release|Any CPU.Build.0 = Release|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Release|x64.ActiveCfg = Release|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Release|x64.Build.0 = Release|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Release|x86.ActiveCfg = Release|Any CPU + {0C1C947F-D99C-4287-B664-858604606AB4}.Release|x86.Build.0 = Release|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Debug|x64.ActiveCfg = Debug|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Debug|x64.Build.0 = Debug|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Debug|x86.ActiveCfg = Debug|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Debug|x86.Build.0 = Debug|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Release|Any CPU.Build.0 = Release|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Release|x64.ActiveCfg = Release|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Release|x64.Build.0 = Release|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Release|x86.ActiveCfg = Release|Any CPU + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577}.Release|x86.Build.0 = Release|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Debug|x64.ActiveCfg = Debug|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Debug|x64.Build.0 = Debug|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Debug|x86.ActiveCfg = Debug|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Debug|x86.Build.0 = Debug|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Release|Any CPU.Build.0 = Release|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Release|x64.ActiveCfg = Release|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Release|x64.Build.0 = Release|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Release|x86.ActiveCfg = Release|Any CPU + {17EDC00D-3329-4839-8A49-86F8CD7D3A15}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C857E6E0-79E7-425A-89AA-2249B8E97C84} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {E5B4B880-6D8F-4224-9134-E009FE1DD84F} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {0C1C947F-D99C-4287-B664-858604606AB4} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {2FBEDAD1-8DD6-48E5-82F0-2A0052B2C577} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {17EDC00D-3329-4839-8A49-86F8CD7D3A15} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md new file mode 100644 index 0000000..2137485 --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# Please Pay Me Bot + +Telegram-бот + web-кабинет для жизни «от зарплаты до зарплаты». + +**Стек сейчас:** Telegram bot (Python) · **API ASP.NET Core + PostgreSQL** (`src/PleasePayMe.*`) · React web. +Python-папка `api/` устарела; HTTP-контракт `/api/...` для бота и web сохранён. + +## Возможности + +- Задать бюджет: сумма + дата до зарплаты +- Пересчёт дневного лимита по остатку и числу дней +- Быстрый ввод трат: `250`, `250 кофе`, `кофе 250` +- Статус / траты за сегодня / отмена последней траты +- Кнопки внизу чата для частых действий + +## Логика лимита + +``` +остаток = бюджет − все траты периода +дней_осталось = (дата_конца − сегодня) + 1 # включая сегодня +остаток_на_утро = остаток + траты_сегодня +лимит_на_день = остаток_на_утро / дней_осталось +можно_сегодня = лимит_на_день − траты_сегодня +``` + +Лимит дня не сжимается от каждой траты внутри дня (модель «утреннего конверта»). Если сегодня вылез из лимита — завтра лимит пересчитается по новому остатку. + +## Быстрый старт + +1. Создай бота у [@BotFather](https://t.me/BotFather), скопируй токен. +2. Установи зависимости: + +```bash +python -m venv .venv +# Windows Git Bash / Linux: +source .venv/Scripts/activate # или .venv/bin/activate +pip install -r requirements.txt +``` + +3. Создай `.env` из примера: + +```bash +cp .env.example .env +# пропиши BOT_TOKEN=... +``` + +4. Запуск: + +```bash +python -m bot.main +``` + +## Команды + +| Команда / кнопка | Действие | +|---|---| +| `/budget` | Новый бюджет (сумма → дата) | +| `/status` | Лимит и остаток | +| `/today` | Список трат за сегодня | +| `/history` | Траты за период бюджета (постранично) | +| `/day 12.09` | Список трат за дату | +| `/spend` | Диалог: трата за другую дату | +| `/undo` | Удалить последнюю трату | +| `/help` | Справка | + +Трата за дату одним сообщением: `за 12.09 250 кофе`, `12.09 250`, `250 кофе 12.09`. + +Данные хранятся локально в SQLite: `data/budget.db`. + +## Web-кабинет (личный, через Telegram) + +Вход через [Telegram Login Widget](https://core.telegram.org/widgets/login). После входа пользователь видит **только свой** бюджет и траты (`/api/me/...`). + +### Настройка BotFather + +1. `/setdomain` → укажи домен, с которого открывается кабинет (нужен публичный HTTPS; чистый `localhost` обычно не работает). +2. Username бота без `@` положи в `.env` как `TELEGRAM_BOT_USERNAME`. + +### API + +| Метод | Путь | Auth | Описание | +|---|---|---|---| +| POST | `/api/auth/telegram` | — | обмен данных Login Widget на JWT | +| GET | `/api/me` | Bearer JWT | профиль | +| GET | `/api/me/budget` | Bearer JWT | свой бюджет | +| GET | `/api/me/expenses` | Bearer JWT | свои траты | +| GET | `/api/budgets`… | `API_TOKEN` | админский список (опционально) | + +### Docker + +```env +BOT_TOKEN=... +TELEGRAM_BOT_USERNAME=MyPayBot +PROXY_URL=socks5://127.0.0.1:10808 +``` + +```bash +docker compose up -d --build +``` + +- кабинет: http://HOST:51290 +- API: http://HOST:51291 + +## Docker + +```bash +docker build -t please-pay-me-bot . +docker run --rm -e BOT_TOKEN=your_token -v "$(pwd)/data:/app/data" please-pay-me-bot +``` + +На Windows (PowerShell): + +```powershell +docker build -t please-pay-me-bot . +docker run --rm -e BOT_TOKEN=your_token -v ${PWD}/data:/app/data please-pay-me-bot +``` + +Том `data` сохраняет SQLite между перезапусками. + +### Прокси (Xray на хосте + бот в Docker) + +**Рекомендуемый способ:** `network_mode: host` — контейнер видит `127.0.0.1` хоста, Xray можно оставить на loopback. + +В `.env` (токен только здесь, не в compose): + +```env +BOT_TOKEN=... +PROXY_URL=socks5://127.0.0.1:10808 +``` + +```bash +docker compose up -d --build +``` + +Проверка на хосте: + +```bash +curl -x socks5h://127.0.0.1:10808 https://api.telegram.org +``` + +Если без `host` сети (bridge): Xray должен слушать `0.0.0.0`, а в `PROXY_URL` — gateway хоста (`172.17.0.1` или `host.docker.internal`). Скрипт: `./scripts/open_xray_listen.sh`. У Portainer custom-сеть часто **не** имеет маршрута к `172.17.0.1`, из‑за этого был timeout. diff --git a/api/DEPRECATED.md b/api/DEPRECATED.md new file mode 100644 index 0000000..5471e9e --- /dev/null +++ b/api/DEPRECATED.md @@ -0,0 +1,6 @@ +# Deprecated Python API + +Этот каталог больше не используется в runtime. + +Актуальный API: `src/PleasePayMe.Api` (ASP.NET Core + PostgreSQL). +Контракт HTTP (`/api/...`) сохранён для web и Telegram-бота. diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..28b07ef --- /dev/null +++ b/api/__init__.py @@ -0,0 +1 @@ +# API package diff --git a/api/config.py b/api/config.py new file mode 100644 index 0000000..b3714a7 --- /dev/null +++ b/api/config.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from pathlib import Path + +from pydantic import field_validator +from pydantic_settings import BaseSettings, SettingsConfigDict + +BASE_DIR = Path(__file__).resolve().parent.parent + + +class ApiSettings(BaseSettings): + model_config = SettingsConfigDict( + env_file=BASE_DIR / ".env", + env_file_encoding="utf-8", + extra="ignore", + ) + + database_path: Path = BASE_DIR / "data" / "budget.db" + bot_token: str + # Shared secret for bot (/api/auth/internal) and admin /api/budgets/* + api_token: str | None = None + jwt_secret: str | None = None + jwt_ttl_seconds: int = 60 * 60 * 24 * 14 + telegram_auth_max_age_seconds: int = 60 * 60 * 24 + cors_origins: str = "*" + host: str = "0.0.0.0" + port: int = 8000 + + @field_validator("bot_token", mode="before") + @classmethod + def require_bot_token(cls, value: object) -> object: + if value is None or (isinstance(value, str) and not value.strip()): + raise ValueError("BOT_TOKEN is required for Telegram login") + return value + + @field_validator("api_token", "jwt_secret", mode="before") + @classmethod + def empty_as_none(cls, value: object) -> object: + if isinstance(value, str) and not value.strip(): + return None + return value + + @property + def session_secret(self) -> str: + return self.jwt_secret or f"ppm-jwt::{self.bot_token}" + + @property + def cors_origin_list(self) -> list[str]: + return [item.strip() for item in self.cors_origins.split(",") if item.strip()] + + +settings = ApiSettings() diff --git a/api/deps.py b/api/deps.py new file mode 100644 index 0000000..f776388 --- /dev/null +++ b/api/deps.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from fastapi import Depends, Header, HTTPException, status +import jwt + +from api.config import settings +from api.tokens import decode_access_token + + +@dataclass(frozen=True) +class AuthUser: + user_id: int + first_name: str | None = None + last_name: str | None = None + username: str | None = None + + +def _extract_bearer(authorization: str | None) -> str | None: + if not authorization: + return None + if authorization.lower().startswith("bearer "): + return authorization[7:].strip() + return None + + +async def require_api_token( + authorization: str | None = Header(default=None), + x_api_token: str | None = Header(default=None, alias="X-API-Token"), +) -> None: + if not settings.api_token: + raise HTTPException( + status_code=status.HTTP_501_NOT_IMPLEMENTED, + detail="Admin API_TOKEN is not configured", + ) + token = (x_api_token or "").strip() or _extract_bearer(authorization) + if not token or token != settings.api_token: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid or missing API token", + ) + + +async def require_user( + authorization: str | None = Header(default=None), +) -> AuthUser: + token = _extract_bearer(authorization) + if not token: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Authorization Bearer token required", + ) + try: + payload = decode_access_token(token) + user_id = int(payload["uid"]) + except (jwt.PyJWTError, KeyError, TypeError, ValueError) as exc: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid or expired session", + ) from exc + + return AuthUser( + user_id=user_id, + first_name=payload.get("fn"), + last_name=payload.get("ln"), + username=payload.get("un"), + ) diff --git a/api/main.py b/api/main.py new file mode 100644 index 0000000..62ec3cb --- /dev/null +++ b/api/main.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from contextlib import asynccontextmanager + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from api.config import settings +from api.routes import router as api_router +from bot.db.database import Database +from bot.db.repository import BudgetRepository +from bot.services.budget import BudgetService + + +@asynccontextmanager +async def lifespan(app: FastAPI): + db = Database(settings.database_path, read_only=False) + await db.connect() + app.state.db = db + app.state.budget_service = BudgetService(BudgetRepository(db)) + try: + yield + finally: + await db.close() + + +app = FastAPI( + title="Please Pay Me API", + version="1.0.0", + lifespan=lifespan, +) + +app.add_middleware( + CORSMiddleware, + allow_origins=settings.cors_origin_list, + allow_credentials=True, + allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], + allow_headers=["Authorization", "X-API-Token", "Content-Type"], +) + + +@app.get("/api/health") +async def health() -> dict[str, str]: + return {"status": "ok"} + + +app.include_router(api_router) diff --git a/api/routes.py b/api/routes.py new file mode 100644 index 0000000..5395df9 --- /dev/null +++ b/api/routes.py @@ -0,0 +1,412 @@ +from __future__ import annotations + +from datetime import date + +from fastapi import APIRouter, Depends, HTTPException, Query, Request, status + +from api.config import settings +from api.deps import AuthUser, require_api_token, require_user +from api.schemas import ( + AuthSessionOut, + AuthUserOut, + BudgetActiveIn, + BudgetCreateIn, + BudgetOut, + BudgetStatusOut, + BudgetUpdateIn, + BudgetUpsertIn, + BudgetsListOut, + ExpenseCreateIn, + ExpenseOut, + ExpensesPageOut, + InternalAuthIn, + TelegramLoginIn, + UndoExpenseOut, +) +from api.telegram_auth import TelegramAuthError, verify_telegram_login +from api.tokens import create_access_token +from bot.services.budget import BudgetService, BudgetStatus, PeriodExpensesPage + +router = APIRouter(prefix="/api") + + +def _status_out(status_data: BudgetStatus) -> BudgetStatusOut: + b = status_data.budget + return BudgetStatusOut( + budget=BudgetOut( + id=b.id, + user_id=b.user_id, + name=b.name, + total_amount=b.total_amount, + start_date=b.start_date, + end_date=b.end_date, + currency=b.currency, + is_active=b.is_active, + ), + today=status_data.today, + days_left=status_data.days_left, + total_spent=status_data.total_spent, + remaining=status_data.remaining, + daily_limit=status_data.daily_limit, + spent_today=status_data.spent_today, + remaining_today=status_data.remaining_today, + is_over_daily=status_data.is_over_daily, + is_over_budget=status_data.is_over_budget, + is_expired=status_data.is_expired, + selected=status_data.selected, + ) + + +def _page_out(page: PeriodExpensesPage) -> ExpensesPageOut: + return ExpensesPageOut( + page=page.page, + total_pages=page.total_pages, + total_count=page.total_count, + total_sum=page.total_sum, + page_size=page.page_size, + budget_id=page.budget.id, + items=[ + ExpenseOut( + id=item.id, + budget_id=item.budget_id, + amount=item.amount, + note=item.note, + spent_at=item.spent_at, + ) + for item in page.items + ], + ) + + +def _service(request: Request) -> BudgetService: + return request.app.state.budget_service + + +@router.post("/auth/telegram", response_model=AuthSessionOut) +async def auth_telegram(body: TelegramLoginIn) -> AuthSessionOut: + try: + profile = verify_telegram_login( + body.model_dump(), + settings.bot_token, + max_age_seconds=settings.telegram_auth_max_age_seconds, + ) + except TelegramAuthError as exc: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=str(exc), + ) from exc + + token = create_access_token(user_id=profile["id"], profile=profile) + return AuthSessionOut( + access_token=token, + user=AuthUserOut( + user_id=profile["id"], + first_name=profile.get("first_name"), + last_name=profile.get("last_name"), + username=profile.get("username"), + photo_url=profile.get("photo_url"), + ), + ) + + +@router.post( + "/auth/internal", + response_model=AuthSessionOut, + dependencies=[Depends(require_api_token)], +) +async def auth_internal(body: InternalAuthIn) -> AuthSessionOut: + profile = { + "id": body.user_id, + "first_name": body.first_name, + "last_name": body.last_name, + "username": body.username, + } + token = create_access_token(user_id=body.user_id, profile=profile) + return AuthSessionOut( + access_token=token, + user=AuthUserOut( + user_id=body.user_id, + first_name=body.first_name, + last_name=body.last_name, + username=body.username, + ), + ) + + +@router.get("/me", response_model=AuthUserOut) +async def me(user: AuthUser = Depends(require_user)) -> AuthUserOut: + return AuthUserOut( + user_id=user.user_id, + first_name=user.first_name, + last_name=user.last_name, + username=user.username, + ) + + +@router.get("/me/budgets", response_model=BudgetsListOut) +async def my_budgets( + request: Request, + user: AuthUser = Depends(require_user), +) -> BudgetsListOut: + service = _service(request) + items = await service.list_user_statuses(user.user_id) + return BudgetsListOut(items=[_status_out(item) for item in items]) + + +@router.post("/me/budgets", response_model=BudgetStatusOut) +async def create_my_budget( + body: BudgetCreateIn, + request: Request, + user: AuthUser = Depends(require_user), +) -> BudgetStatusOut: + service = _service(request) + try: + status_data = await service.create_budget( + user_id=user.user_id, + total_amount=body.total_amount, + end_date=body.end_date, + name=body.name, + is_active=body.is_active, + select=body.select, + ) + except ValueError as exc: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(exc), + ) from exc + return _status_out(status_data) + + +@router.put("/me/budgets/{budget_id}", response_model=BudgetStatusOut) +async def update_my_budget( + budget_id: int, + body: BudgetUpdateIn, + request: Request, + user: AuthUser = Depends(require_user), +) -> BudgetStatusOut: + service = _service(request) + try: + status_data = await service.update_budget( + user.user_id, + budget_id, + name=body.name, + total_amount=body.total_amount, + end_date=body.end_date, + reset_expenses=body.reset_expenses, + ) + except ValueError as exc: + code = ( + status.HTTP_404_NOT_FOUND + if "не найден" in str(exc).lower() + else status.HTTP_400_BAD_REQUEST + ) + raise HTTPException(status_code=code, detail=str(exc)) from exc + return _status_out(status_data) + + +@router.patch("/me/budgets/{budget_id}/active", response_model=BudgetStatusOut) +async def set_my_budget_active( + budget_id: int, + body: BudgetActiveIn, + request: Request, + user: AuthUser = Depends(require_user), +) -> BudgetStatusOut: + service = _service(request) + try: + status_data = await service.set_budget_active( + user.user_id, + budget_id, + body.is_active, + ) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc + return _status_out(status_data) + + +@router.post("/me/budgets/{budget_id}/select", response_model=BudgetStatusOut) +async def select_my_budget( + budget_id: int, + request: Request, + user: AuthUser = Depends(require_user), +) -> BudgetStatusOut: + service = _service(request) + try: + status_data = await service.select_budget(user.user_id, budget_id) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc + return _status_out(status_data) + + +@router.get("/me/budget", response_model=BudgetStatusOut) +async def my_budget( + request: Request, + user: AuthUser = Depends(require_user), + budget_id: int | None = Query(default=None), +) -> BudgetStatusOut: + service = _service(request) + try: + status_data = await service.get_status(user.user_id, budget_id=budget_id) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc + return _status_out(status_data) + + +@router.get("/me/expenses", response_model=ExpensesPageOut) +async def my_expenses( + request: Request, + user: AuthUser = Depends(require_user), + page: int = Query(default=0, ge=0), + page_size: int = Query(default=20, ge=1, le=100), + spent_at: date | None = Query(default=None), + budget_id: int | None = Query(default=None), +) -> ExpensesPageOut: + service = _service(request) + try: + if spent_at is not None: + page_data = await service.get_expenses_on_date_page( + user.user_id, + spent_at, + page=page, + page_size=page_size, + budget_id=budget_id, + ) + else: + page_data = await service.get_period_expenses_page( + user.user_id, + page=page, + page_size=page_size, + budget_id=budget_id, + ) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc + return _page_out(page_data) + + +@router.post("/me/expenses", response_model=BudgetStatusOut) +async def create_my_expense( + body: ExpenseCreateIn, + request: Request, + user: AuthUser = Depends(require_user), +) -> BudgetStatusOut: + service = _service(request) + try: + status_data = await service.add_expense( + user_id=user.user_id, + amount=body.amount, + note=body.note, + spent_at=body.spent_at, + budget_id=body.budget_id, + ) + except ValueError as exc: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(exc), + ) from exc + return _status_out(status_data) + + +@router.delete("/me/expenses/last", response_model=UndoExpenseOut) +async def undo_my_last_expense( + request: Request, + user: AuthUser = Depends(require_user), + budget_id: int | None = Query(default=None), +) -> UndoExpenseOut: + service = _service(request) + status_data, amount = await service.undo_last_expense( + user.user_id, + budget_id=budget_id, + ) + if amount is None or status_data is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Нечего отменять", + ) + return UndoExpenseOut(deleted_amount=amount, status=_status_out(status_data)) + + +@router.put("/me/budget", response_model=BudgetStatusOut) +async def upsert_my_budget( + body: BudgetUpsertIn, + request: Request, + user: AuthUser = Depends(require_user), +) -> BudgetStatusOut: + service = _service(request) + try: + status_data = await service.set_budget( + user_id=user.user_id, + total_amount=body.total_amount, + end_date=body.end_date, + reset_expenses=body.reset_expenses, + name=body.name, + budget_id=body.budget_id, + ) + except ValueError as exc: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=str(exc), + ) from exc + return _status_out(status_data) + + +@router.get( + "/budgets", + response_model=BudgetsListOut, + dependencies=[Depends(require_api_token)], +) +async def list_budgets(request: Request) -> BudgetsListOut: + service = _service(request) + items = await service.list_budget_summaries() + return BudgetsListOut(items=[_status_out(item) for item in items]) + + +@router.get( + "/budgets/{user_id}", + response_model=BudgetStatusOut, + dependencies=[Depends(require_api_token)], +) +async def get_budget( + user_id: int, + request: Request, + budget_id: int | None = Query(default=None), +) -> BudgetStatusOut: + service = _service(request) + try: + status_data = await service.get_status(user_id, budget_id=budget_id) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc + return _status_out(status_data) + + +@router.get( + "/budgets/{user_id}/expenses", + response_model=ExpensesPageOut, + dependencies=[Depends(require_api_token)], +) +async def list_expenses( + user_id: int, + request: Request, + page: int = Query(default=0, ge=0), + page_size: int = Query(default=20, ge=1, le=100), + spent_at: date | None = Query(default=None), + budget_id: int | None = Query(default=None), +) -> ExpensesPageOut: + service = _service(request) + try: + if spent_at is not None: + page_data = await service.get_expenses_on_date_page( + user_id, + spent_at, + page=page, + page_size=page_size, + budget_id=budget_id, + ) + else: + page_data = await service.get_period_expenses_page( + user_id, + page=page, + page_size=page_size, + budget_id=budget_id, + ) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc + return _page_out(page_data) diff --git a/api/schemas.py b/api/schemas.py new file mode 100644 index 0000000..2dd701b --- /dev/null +++ b/api/schemas.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +from datetime import date + +from pydantic import BaseModel, Field + + +class BudgetOut(BaseModel): + id: int + user_id: int + name: str + total_amount: float + start_date: date + end_date: date + currency: str + is_active: bool + + +class BudgetStatusOut(BaseModel): + budget: BudgetOut + today: date + days_left: int + total_spent: float + remaining: float + daily_limit: float + spent_today: float + remaining_today: float + is_over_daily: bool + is_over_budget: bool + is_expired: bool + selected: bool = False + + +class ExpenseOut(BaseModel): + id: int + budget_id: int + amount: float + note: str | None + spent_at: date + + +class ExpensesPageOut(BaseModel): + page: int + total_pages: int + total_count: int + total_sum: float + page_size: int + budget_id: int + items: list[ExpenseOut] + + +class BudgetsListOut(BaseModel): + items: list[BudgetStatusOut] = Field(default_factory=list) + + +class TelegramLoginIn(BaseModel): + id: int + first_name: str + last_name: str | None = None + username: str | None = None + photo_url: str | None = None + auth_date: int + hash: str + + +class InternalAuthIn(BaseModel): + """Service auth for trusted clients (Telegram bot) acting as a user.""" + + user_id: int = Field(gt=0) + first_name: str | None = None + last_name: str | None = None + username: str | None = None + + +class AuthUserOut(BaseModel): + user_id: int + first_name: str | None = None + last_name: str | None = None + username: str | None = None + photo_url: str | None = None + + +class AuthSessionOut(BaseModel): + access_token: str + token_type: str = "bearer" + user: AuthUserOut + + +class ExpenseCreateIn(BaseModel): + amount: float = Field(gt=0) + note: str | None = None + spent_at: date | None = None + budget_id: int | None = None + + +class BudgetCreateIn(BaseModel): + total_amount: float = Field(gt=0) + end_date: date + name: str = "Бюджет" + is_active: bool = True + select: bool = True + + +class BudgetUpdateIn(BaseModel): + total_amount: float | None = Field(default=None, gt=0) + end_date: date | None = None + name: str | None = None + reset_expenses: bool = False + + +class BudgetActiveIn(BaseModel): + is_active: bool + + +class BudgetUpsertIn(BaseModel): + """Legacy upsert against selected budget (or create if none).""" + + total_amount: float = Field(gt=0) + end_date: date + reset_expenses: bool = True + name: str | None = None + budget_id: int | None = None + + +class UndoExpenseOut(BaseModel): + deleted_amount: float + status: BudgetStatusOut diff --git a/api/telegram_auth.py b/api/telegram_auth.py new file mode 100644 index 0000000..ff4f338 --- /dev/null +++ b/api/telegram_auth.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +import hashlib +import hmac +import time +from typing import Any + + +class TelegramAuthError(ValueError): + pass + + +def verify_telegram_login( + payload: dict[str, Any], + bot_token: str, + *, + max_age_seconds: int = 86400, +) -> dict[str, Any]: + """Verify Telegram Login Widget data per Telegram docs.""" + received_hash = payload.get("hash") + if not received_hash or not isinstance(received_hash, str): + raise TelegramAuthError("Missing hash") + + check_pairs: list[str] = [] + for key in sorted(payload.keys()): + if key == "hash": + continue + value = payload[key] + if value is None: + continue + check_pairs.append(f"{key}={value}") + data_check_string = "\n".join(check_pairs) + + secret_key = hashlib.sha256(bot_token.encode("utf-8")).digest() + calculated = hmac.new( + secret_key, + data_check_string.encode("utf-8"), + hashlib.sha256, + ).hexdigest() + + if not hmac.compare_digest(calculated, received_hash): + raise TelegramAuthError("Invalid Telegram login signature") + + auth_date_raw = payload.get("auth_date") + try: + auth_date = int(auth_date_raw) + except (TypeError, ValueError) as exc: + raise TelegramAuthError("Invalid auth_date") from exc + + if max_age_seconds > 0 and time.time() - auth_date > max_age_seconds: + raise TelegramAuthError("Telegram login data expired") + + try: + user_id = int(payload["id"]) + except (KeyError, TypeError, ValueError) as exc: + raise TelegramAuthError("Missing Telegram user id") from exc + + return { + "id": user_id, + "first_name": str(payload.get("first_name") or ""), + "last_name": str(payload.get("last_name") or "") or None, + "username": str(payload.get("username") or "") or None, + "photo_url": str(payload.get("photo_url") or "") or None, + "auth_date": auth_date, + } diff --git a/api/tokens.py b/api/tokens.py new file mode 100644 index 0000000..8bace74 --- /dev/null +++ b/api/tokens.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from datetime import datetime, timedelta, timezone +from typing import Any + +import jwt + +from api.config import settings + + +def create_access_token(*, user_id: int, profile: dict[str, Any]) -> str: + now = datetime.now(timezone.utc) + payload = { + "sub": str(user_id), + "uid": user_id, + "fn": profile.get("first_name"), + "ln": profile.get("last_name"), + "un": profile.get("username"), + "iat": now, + "exp": now + timedelta(seconds=settings.jwt_ttl_seconds), + } + return jwt.encode(payload, settings.session_secret, algorithm="HS256") + + +def decode_access_token(token: str) -> dict[str, Any]: + return jwt.decode(token, settings.session_secret, algorithms=["HS256"]) diff --git a/bot/__init__.py b/bot/__init__.py new file mode 100644 index 0000000..0e632e1 --- /dev/null +++ b/bot/__init__.py @@ -0,0 +1 @@ +# Package marker diff --git a/bot/clients/__init__.py b/bot/clients/__init__.py new file mode 100644 index 0000000..6694ec6 --- /dev/null +++ b/bot/clients/__init__.py @@ -0,0 +1 @@ +"""HTTP clients for talking to the Please Pay Me API.""" diff --git a/bot/clients/budget_api.py b/bot/clients/budget_api.py new file mode 100644 index 0000000..b08e4ba --- /dev/null +++ b/bot/clients/budget_api.py @@ -0,0 +1,444 @@ +from __future__ import annotations + +import base64 +import json +from datetime import date, datetime, timedelta, timezone +from typing import Any + +import aiohttp + +from bot.db.repository import Budget, Expense +from bot.services.budget import ( + PERIOD_PAGE_SIZE, + BudgetStatus, + PeriodExpensesPage, +) + + +YANDEX_NAMESPACE_BIT = 1 << 50 + + +class BudgetApiError(Exception): + """HTTP / transport failure talking to the API.""" + + +class YandexLoginRequired(Exception): + """Bot identity is not linked to a Yandex account yet.""" + + def __init__(self, login_url: str, detail: str = "") -> None: + self.login_url = login_url + self.detail = detail or "Чтобы пользоваться ботом, войдите через Яндекс." + super().__init__(self.detail) + + +def _jwt_uid(token: str) -> int | None: + try: + payload = token.split(".")[1] + payload += "=" * (-len(payload) % 4) + data = json.loads(base64.urlsafe_b64decode(payload.encode("ascii"))) + raw = data.get("uid") or data.get("sub") + return int(raw) + except (IndexError, ValueError, TypeError, json.JSONDecodeError, OSError): + return None + + +class BudgetApiClient: + """Budget operations via the same HTTP API as the web cabinet.""" + + def __init__( + self, + *, + base_url: str, + api_token: str, + session: aiohttp.ClientSession | None = None, + ) -> None: + self._base = base_url.rstrip("/") + self._api_token = api_token + self._session = session + self._owns_session = session is None + self._tokens: dict[int, tuple[str, datetime]] = {} + + async def start(self) -> None: + if self._session is None: + self._session = aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=30), + ) + + async def close(self) -> None: + if self._owns_session and self._session is not None: + await self._session.close() + self._session = None + + def _ensure_session(self) -> aiohttp.ClientSession: + if self._session is None: + raise RuntimeError("BudgetApiClient is not started") + return self._session + + @staticmethod + def _detail(payload: Any, fallback: str) -> str: + if isinstance(payload, dict): + detail = payload.get("detail", fallback) + if isinstance(detail, str): + return detail + if isinstance(detail, list) and detail: + first = detail[0] + if isinstance(first, dict) and "msg" in first: + return str(first["msg"]) + return str(first) + return str(detail) + return fallback + + async def _request( + self, + method: str, + path: str, + *, + user_id: int | None = None, + json_body: dict[str, Any] | None = None, + params: dict[str, Any] | None = None, + retry_auth: bool = True, + ) -> Any: + session = self._ensure_session() + headers: dict[str, str] = {"Accept": "application/json"} + if user_id is not None: + headers["Authorization"] = f"Bearer {await self._access_token(user_id)}" + else: + headers["X-API-Token"] = self._api_token + + clean_params = None + if params: + clean_params = {k: v for k, v in params.items() if v is not None} + + url = f"{self._base}{path}" + async with session.request( + method, + url, + json=json_body, + params=clean_params, + headers=headers, + ) as resp: + if resp.status == 401 and user_id is not None and retry_auth: + self._tokens.pop(user_id, None) + await resp.read() + return await self._request( + method, + path, + user_id=user_id, + json_body=json_body, + params=params, + retry_auth=False, + ) + + if resp.status == 204: + return None + + body: Any + try: + body = await resp.json(content_type=None) + except aiohttp.ContentTypeError: + text = await resp.text() + body = {"detail": text or resp.reason} + + if resp.status >= 400: + if ( + resp.status == 403 + and isinstance(body, dict) + and body.get("code") == "yandex_required" + ): + if user_id is not None: + self._tokens.pop(user_id, None) + raise YandexLoginRequired( + str(body.get("login_url") or ""), + self._detail(body, "Чтобы пользоваться ботом, войдите через Яндекс."), + ) + message = self._detail(body, f"API error {resp.status}") + if resp.status in {400, 404}: + raise ValueError(message) + raise BudgetApiError(message) + return body + + async def ensure_yandex_login(self, telegram_user_id: int) -> None: + await self._access_token(telegram_user_id) + + async def _access_token(self, user_id: int) -> str: + now = datetime.now(timezone.utc) + cached = self._tokens.get(user_id) + if cached and cached[1] > now + timedelta(seconds=60): + uid = _jwt_uid(cached[0]) + if uid is not None and (uid & YANDEX_NAMESPACE_BIT): + return cached[0] + self._tokens.pop(user_id, None) + + data = await self._request( + "POST", + "/api/auth/internal", + json_body={"user_id": user_id}, + ) + token = str(data["access_token"]) + uid = _jwt_uid(token) + if uid is None or not (uid & YANDEX_NAMESPACE_BIT): + raise BudgetApiError("API did not issue a Yandex-linked session") + self._tokens[user_id] = (token, now + timedelta(days=1)) + return token + + @staticmethod + def _parse_date(value: str | date) -> date: + if isinstance(value, date) and not isinstance(value, datetime): + return value + return date.fromisoformat(str(value)[:10]) + + def _status_from(self, data: dict[str, Any]) -> BudgetStatus: + b = data["budget"] + budget = Budget( + id=int(b["id"]), + user_id=int(b["user_id"]), + name=str(b.get("name") or "Бюджет"), + total_amount=float(b["total_amount"]), + start_date=self._parse_date(b["start_date"]), + end_date=self._parse_date(b["end_date"]), + currency=str(b.get("currency") or "RUB"), + is_active=bool(b.get("is_active", True)), + ) + return BudgetStatus( + budget=budget, + today=self._parse_date(data["today"]), + days_left=int(data["days_left"]), + total_spent=float(data["total_spent"]), + remaining=float(data["remaining"]), + daily_limit=float(data["daily_limit"]), + spent_today=float(data["spent_today"]), + remaining_today=float(data["remaining_today"]), + is_over_daily=bool(data["is_over_daily"]), + is_over_budget=bool(data["is_over_budget"]), + is_expired=bool(data["is_expired"]), + selected=bool(data.get("selected", False)), + ) + + def _expense_from(self, data: dict[str, Any], user_id: int) -> Expense: + return Expense( + id=int(data["id"]), + user_id=user_id, + budget_id=int(data.get("budget_id") or 0), + amount=float(data["amount"]), + note=data.get("note"), + spent_at=self._parse_date(data["spent_at"]), + ) + + async def _page_from( + self, + user_id: int, + data: dict[str, Any], + budget_id: int | None = None, + ) -> PeriodExpensesPage: + status = await self.get_status( + user_id, + budget_id=budget_id or int(data.get("budget_id") or 0) or None, + ) + return PeriodExpensesPage( + budget=status.budget, + page=int(data["page"]), + total_pages=int(data["total_pages"]), + total_count=int(data["total_count"]), + total_sum=float(data["total_sum"]), + page_size=int(data["page_size"]), + items=[self._expense_from(item, user_id) for item in data.get("items", [])], + ) + + async def list_user_statuses(self, user_id: int) -> list[BudgetStatus]: + data = await self._request("GET", "/api/me/budgets", user_id=user_id) + return [self._status_from(item) for item in data.get("items", [])] + + async def get_status( + self, + user_id: int, + today: date | None = None, + budget_id: int | None = None, + ) -> BudgetStatus: + del today + data = await self._request( + "GET", + "/api/me/budget", + user_id=user_id, + params={"budget_id": budget_id} if budget_id else None, + ) + return self._status_from(data) + + async def create_budget( + self, + user_id: int, + total_amount: float, + end_date: date, + *, + name: str = "Бюджет", + start_date: date | None = None, + is_active: bool = True, + select: bool = True, + ) -> BudgetStatus: + body: dict[str, Any] = { + "total_amount": total_amount, + "end_date": end_date.isoformat(), + "name": name, + "is_active": is_active, + "select": select, + } + if start_date is not None: + body["start_date"] = start_date.isoformat() + data = await self._request( + "POST", + "/api/me/budgets", + user_id=user_id, + json_body=body, + ) + return self._status_from(data) + + async def set_budget_active( + self, + user_id: int, + budget_id: int, + is_active: bool, + ) -> BudgetStatus: + data = await self._request( + "PATCH", + f"/api/me/budgets/{budget_id}/active", + user_id=user_id, + json_body={"is_active": is_active}, + ) + return self._status_from(data) + + async def select_budget(self, user_id: int, budget_id: int) -> BudgetStatus: + data = await self._request( + "POST", + f"/api/me/budgets/{budget_id}/select", + user_id=user_id, + ) + return self._status_from(data) + + async def delete_budget(self, user_id: int, budget_id: int) -> None: + await self._request( + "DELETE", + f"/api/me/budgets/{budget_id}", + user_id=user_id, + ) + + async def set_budget( + self, + user_id: int, + total_amount: float, + end_date: date, + start_date: date | None = None, + reset_expenses: bool = True, + name: str | None = None, + budget_id: int | None = None, + ) -> BudgetStatus: + # Creating a new named budget is the common bot flow. + if budget_id is None and name is not None: + return await self.create_budget( + user_id, + total_amount, + end_date, + name=name, + start_date=start_date, + ) + body: dict[str, Any] = { + "total_amount": total_amount, + "end_date": end_date.isoformat(), + "reset_expenses": reset_expenses, + } + if name is not None: + body["name"] = name + if budget_id is not None: + body["budget_id"] = budget_id + if start_date is not None: + body["start_date"] = start_date.isoformat() + data = await self._request( + "PUT", + "/api/me/budget", + user_id=user_id, + json_body=body, + ) + return self._status_from(data) + + async def add_expense( + self, + user_id: int, + amount: float, + note: str | None = None, + spent_at: date | None = None, + budget_id: int | None = None, + ) -> BudgetStatus: + body: dict[str, Any] = {"amount": amount} + if note is not None: + body["note"] = note + if spent_at is not None: + body["spent_at"] = spent_at.isoformat() + if budget_id is not None: + body["budget_id"] = budget_id + data = await self._request( + "POST", + "/api/me/expenses", + user_id=user_id, + json_body=body, + ) + return self._status_from(data) + + async def undo_last_expense( + self, + user_id: int, + budget_id: int | None = None, + ) -> tuple[BudgetStatus | None, float | None]: + try: + data = await self._request( + "DELETE", + "/api/me/expenses/last", + user_id=user_id, + params={"budget_id": budget_id} if budget_id else None, + ) + except ValueError: + return None, None + return self._status_from(data["status"]), float(data["deleted_amount"]) + + async def get_period_expenses_page( + self, + user_id: int, + page: int = 0, + page_size: int = PERIOD_PAGE_SIZE, + budget_id: int | None = None, + ) -> PeriodExpensesPage: + data = await self._request( + "GET", + "/api/me/expenses", + user_id=user_id, + params={"page": page, "page_size": page_size, "budget_id": budget_id}, + ) + return await self._page_from(user_id, data, budget_id=budget_id) + + async def expenses_on_date( + self, + user_id: int, + day: date, + budget_id: int | None = None, + ) -> list[Expense]: + data = await self._request( + "GET", + "/api/me/expenses", + user_id=user_id, + params={ + "spent_at": day.isoformat(), + "page": 0, + "page_size": 100, + "budget_id": budget_id, + }, + ) + return [self._expense_from(item, user_id) for item in data.get("items", [])] + + async def today_expenses( + self, + user_id: int, + today: date | None = None, + budget_id: int | None = None, + ) -> list[Expense]: + return await self.expenses_on_date( + user_id, + today or date.today(), + budget_id=budget_id, + ) diff --git a/bot/config.py b/bot/config.py new file mode 100644 index 0000000..e79df9b --- /dev/null +++ b/bot/config.py @@ -0,0 +1,47 @@ +from pathlib import Path + +from pydantic import field_validator +from pydantic_settings import BaseSettings, SettingsConfigDict + +BASE_DIR = Path(__file__).resolve().parent.parent + + +class Settings(BaseSettings): + model_config = SettingsConfigDict( + env_file=BASE_DIR / ".env", + env_file_encoding="utf-8", + extra="ignore", + ) + + bot_token: str + # Same API the web cabinet uses (bot no longer opens SQLite). + api_base_url: str = "http://127.0.0.1:51291" + api_token: str + # Example for Docker → host Xray: socks5://127.0.0.1:10808 + proxy_url: str | None = None + + @field_validator("proxy_url", mode="before") + @classmethod + def empty_proxy_as_none(cls, value: object) -> object: + if value is None: + return None + if isinstance(value, str) and not value.strip(): + return None + return value + + @field_validator("api_token", mode="before") + @classmethod + def require_api_token(cls, value: object) -> object: + if value is None or (isinstance(value, str) and not value.strip()): + raise ValueError( + "API_TOKEN is required: bot authenticates to the API with it" + ) + return value + + @field_validator("api_base_url") + @classmethod + def normalize_base_url(cls, value: str) -> str: + return value.rstrip("/") + + +settings = Settings() diff --git a/bot/db/__init__.py b/bot/db/__init__.py new file mode 100644 index 0000000..0e632e1 --- /dev/null +++ b/bot/db/__init__.py @@ -0,0 +1 @@ +# Package marker diff --git a/bot/db/database.py b/bot/db/database.py new file mode 100644 index 0000000..cbb3e73 --- /dev/null +++ b/bot/db/database.py @@ -0,0 +1,243 @@ +from __future__ import annotations + +from pathlib import Path + +import aiosqlite + +SCHEMA = """ +CREATE TABLE IF NOT EXISTS users ( + user_id INTEGER PRIMARY KEY, + selected_budget_id INTEGER, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS budgets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + name TEXT NOT NULL DEFAULT '', + total_amount REAL NOT NULL, + start_date TEXT NOT NULL, + end_date TEXT NOT NULL, + currency TEXT NOT NULL DEFAULT 'RUB', + is_active INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS expenses ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + budget_id INTEGER NOT NULL, + amount REAL NOT NULL, + note TEXT, + spent_at TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, + FOREIGN KEY (budget_id) REFERENCES budgets(id) ON DELETE CASCADE +); +""" + + +async def _ensure_indexes(conn: aiosqlite.Connection) -> None: + await conn.executescript( + """ + CREATE INDEX IF NOT EXISTS idx_budgets_user + ON budgets(user_id, is_active, id); + CREATE INDEX IF NOT EXISTS idx_expenses_budget_spent_at + ON expenses(budget_id, spent_at); + CREATE INDEX IF NOT EXISTS idx_expenses_user_spent_at + ON expenses(user_id, spent_at); + """ + ) + + +async def _table_columns(conn: aiosqlite.Connection, table: str) -> set[str]: + cursor = await conn.execute(f"PRAGMA table_info({table})") + rows = await cursor.fetchall() + return {str(row[1]) for row in rows} + + +async def _has_unique_user_on_budgets(conn: aiosqlite.Connection) -> bool: + cursor = await conn.execute("PRAGMA index_list(budgets)") + indexes = await cursor.fetchall() + for idx in indexes: + # (seq, name, unique, origin, partial) + if not idx[2]: + continue + name = idx[1] + info = await conn.execute(f"PRAGMA index_info({name})") + cols = [row[2] for row in await info.fetchall()] + if cols == ["user_id"]: + return True + return False + + +async def migrate_schema(conn: aiosqlite.Connection) -> None: + """Upgrade legacy one-budget-per-user schema in place.""" + tables = { + row[0] + for row in await ( + await conn.execute( + "SELECT name FROM sqlite_master WHERE type='table'" + ) + ).fetchall() + } + if "budgets" not in tables: + return + + budget_cols = await _table_columns(conn, "budgets") + if "name" not in budget_cols: + await conn.execute( + "ALTER TABLE budgets ADD COLUMN name TEXT NOT NULL DEFAULT ''" + ) + if "is_active" not in budget_cols: + await conn.execute( + "ALTER TABLE budgets ADD COLUMN is_active INTEGER NOT NULL DEFAULT 1" + ) + + user_cols = await _table_columns(conn, "users") + if "selected_budget_id" not in user_cols: + await conn.execute( + "ALTER TABLE users ADD COLUMN selected_budget_id INTEGER" + ) + + if await _has_unique_user_on_budgets(conn): + await conn.executescript( + """ + CREATE TABLE budgets_migrated ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + name TEXT NOT NULL DEFAULT '', + total_amount REAL NOT NULL, + start_date TEXT NOT NULL, + end_date TEXT NOT NULL, + currency TEXT NOT NULL DEFAULT 'RUB', + is_active INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE + ); + INSERT INTO budgets_migrated ( + id, user_id, name, total_amount, start_date, end_date, + currency, is_active, created_at + ) + SELECT + id, user_id, + COALESCE(NULLIF(name, ''), 'Бюджет'), + total_amount, start_date, end_date, currency, + COALESCE(is_active, 1), created_at + FROM budgets; + DROP TABLE budgets; + ALTER TABLE budgets_migrated RENAME TO budgets; + """ + ) + + expense_cols = await _table_columns(conn, "expenses") + if "budget_id" not in expense_cols: + await conn.execute("ALTER TABLE expenses ADD COLUMN budget_id INTEGER") + await conn.execute( + """ + UPDATE expenses + SET budget_id = ( + SELECT b.id FROM budgets b + WHERE b.user_id = expenses.user_id + ORDER BY b.id DESC + LIMIT 1 + ) + WHERE budget_id IS NULL + """ + ) + # Drop orphan expenses that have no budget (should be rare) + await conn.execute("DELETE FROM expenses WHERE budget_id IS NULL") + await conn.executescript( + """ + CREATE TABLE expenses_migrated ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + budget_id INTEGER NOT NULL, + amount REAL NOT NULL, + note TEXT, + spent_at TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, + FOREIGN KEY (budget_id) REFERENCES budgets(id) ON DELETE CASCADE + ); + INSERT INTO expenses_migrated ( + id, user_id, budget_id, amount, note, spent_at, created_at + ) + SELECT id, user_id, budget_id, amount, note, spent_at, created_at + FROM expenses; + DROP TABLE expenses; + ALTER TABLE expenses_migrated RENAME TO expenses; + """ + ) + + # Backfill selected budget for users who have budgets + await conn.execute( + """ + UPDATE users + SET selected_budget_id = ( + SELECT b.id FROM budgets b + WHERE b.user_id = users.user_id + ORDER BY b.is_active DESC, b.id DESC + LIMIT 1 + ) + WHERE selected_budget_id IS NULL + AND EXISTS (SELECT 1 FROM budgets b WHERE b.user_id = users.user_id) + """ + ) + await conn.execute( + """ + UPDATE budgets + SET name = 'Бюджет' + WHERE name IS NULL OR TRIM(name) = '' + """ + ) + await _ensure_indexes(conn) + + +class Database: + def __init__(self, path: Path, *, read_only: bool = False) -> None: + self.path = path + self.read_only = read_only + self._conn: aiosqlite.Connection | None = None + + async def connect(self) -> None: + if self.read_only: + if not self.path.exists(): + raise FileNotFoundError( + f"Database not found: {self.path}. " + "Сначала запусти API, чтобы создался data/budget.db" + ) + uri = f"file:{self.path.resolve().as_posix()}?mode=ro" + self._conn = await aiosqlite.connect(uri, uri=True) + self._conn.row_factory = aiosqlite.Row + await self._conn.execute("PRAGMA foreign_keys = ON") + return + + self.path.parent.mkdir(parents=True, exist_ok=True) + self._conn = await aiosqlite.connect(self.path) + self._conn.row_factory = aiosqlite.Row + await self._conn.execute("PRAGMA foreign_keys = ON") + try: + await self._conn.execute("PRAGMA journal_mode=WAL") + except aiosqlite.OperationalError: + pass + await self._conn.executescript(SCHEMA) + await migrate_schema(self._conn) + # Fresh DBs: migrate may no-op early if tables were just created with + # full columns — still ensure indexes exist. + budget_cols = await _table_columns(self._conn, "budgets") + if "is_active" in budget_cols: + await _ensure_indexes(self._conn) + await self._conn.commit() + + async def close(self) -> None: + if self._conn is not None: + await self._conn.close() + self._conn = None + + @property + def conn(self) -> aiosqlite.Connection: + if self._conn is None: + raise RuntimeError("Database is not connected") + return self._conn diff --git a/bot/db/repository.py b/bot/db/repository.py new file mode 100644 index 0000000..193bbb8 --- /dev/null +++ b/bot/db/repository.py @@ -0,0 +1,400 @@ +from __future__ import annotations + +from dataclasses import dataclass +from datetime import date +from typing import Any + +from bot.db.database import Database + + +@dataclass(frozen=True) +class Budget: + id: int + user_id: int + name: str + total_amount: float + start_date: date + end_date: date + currency: str + is_active: bool + + +@dataclass(frozen=True) +class Expense: + id: int + user_id: int + budget_id: int + amount: float + note: str | None + spent_at: date + + +def _parse_date(value: str) -> date: + return date.fromisoformat(value[:10]) + + +def _row_to_budget(row: Any) -> Budget: + return Budget( + id=int(row["id"]), + user_id=int(row["user_id"]), + name=str(row["name"] or "Бюджет"), + total_amount=float(row["total_amount"]), + start_date=_parse_date(row["start_date"]), + end_date=_parse_date(row["end_date"]), + currency=str(row["currency"] or "RUB"), + is_active=bool(row["is_active"]), + ) + + +def _row_to_expense(row: Any) -> Expense: + return Expense( + id=int(row["id"]), + user_id=int(row["user_id"]), + budget_id=int(row["budget_id"]), + amount=float(row["amount"]), + note=row["note"], + spent_at=_parse_date(row["spent_at"]), + ) + + +class BudgetRepository: + def __init__(self, db: Database) -> None: + self._db = db + + async def ensure_user(self, user_id: int) -> None: + await self._db.conn.execute( + "INSERT OR IGNORE INTO users(user_id) VALUES (?)", + (user_id,), + ) + await self._db.conn.commit() + + async def get_selected_budget_id(self, user_id: int) -> int | None: + cursor = await self._db.conn.execute( + "SELECT selected_budget_id FROM users WHERE user_id = ?", + (user_id,), + ) + row = await cursor.fetchone() + if row is None or row["selected_budget_id"] is None: + return None + return int(row["selected_budget_id"]) + + async def set_selected_budget_id(self, user_id: int, budget_id: int | None) -> None: + await self.ensure_user(user_id) + await self._db.conn.execute( + "UPDATE users SET selected_budget_id = ? WHERE user_id = ?", + (budget_id, user_id), + ) + await self._db.conn.commit() + + async def create_budget( + self, + user_id: int, + *, + name: str, + total_amount: float, + start_date: date, + end_date: date, + currency: str = "RUB", + is_active: bool = True, + select: bool = True, + ) -> Budget: + await self.ensure_user(user_id) + cursor = await self._db.conn.execute( + """ + INSERT INTO budgets( + user_id, name, total_amount, start_date, end_date, currency, is_active + ) + VALUES (?, ?, ?, ?, ?, ?, ?) + RETURNING * + """, + ( + user_id, + name.strip() or "Бюджет", + total_amount, + start_date.isoformat(), + end_date.isoformat(), + currency, + 1 if is_active else 0, + ), + ) + row = await cursor.fetchone() + await self._db.conn.commit() + if row is None: + raise RuntimeError("Failed to create budget") + budget = _row_to_budget(row) + if select: + await self.set_selected_budget_id(user_id, budget.id) + return budget + + async def update_budget( + self, + budget_id: int, + user_id: int, + *, + name: str | None = None, + total_amount: float | None = None, + start_date: date | None = None, + end_date: date | None = None, + currency: str | None = None, + ) -> Budget: + budget = await self.get_budget_for_user(budget_id, user_id) + if budget is None: + raise ValueError("Бюджет не найден") + + next_name = name.strip() if name is not None else budget.name + next_total = total_amount if total_amount is not None else budget.total_amount + next_start = start_date if start_date is not None else budget.start_date + next_end = end_date if end_date is not None else budget.end_date + next_currency = currency if currency is not None else budget.currency + + await self._db.conn.execute( + """ + UPDATE budgets + SET name = ?, total_amount = ?, start_date = ?, end_date = ?, currency = ? + WHERE id = ? AND user_id = ? + """, + ( + next_name or "Бюджет", + next_total, + next_start.isoformat(), + next_end.isoformat(), + next_currency, + budget_id, + user_id, + ), + ) + await self._db.conn.commit() + updated = await self.get_budget_for_user(budget_id, user_id) + if updated is None: + raise RuntimeError("Failed to update budget") + return updated + + async def set_budget_active( + self, + budget_id: int, + user_id: int, + is_active: bool, + ) -> Budget: + budget = await self.get_budget_for_user(budget_id, user_id) + if budget is None: + raise ValueError("Бюджет не найден") + await self._db.conn.execute( + "UPDATE budgets SET is_active = ? WHERE id = ? AND user_id = ?", + (1 if is_active else 0, budget_id, user_id), + ) + await self._db.conn.commit() + updated = await self.get_budget_for_user(budget_id, user_id) + if updated is None: + raise RuntimeError("Failed to update budget activity") + return updated + + async def get_budget_by_id(self, budget_id: int) -> Budget | None: + cursor = await self._db.conn.execute( + "SELECT * FROM budgets WHERE id = ?", + (budget_id,), + ) + row = await cursor.fetchone() + return _row_to_budget(row) if row else None + + async def get_budget_for_user(self, budget_id: int, user_id: int) -> Budget | None: + cursor = await self._db.conn.execute( + "SELECT * FROM budgets WHERE id = ? AND user_id = ?", + (budget_id, user_id), + ) + row = await cursor.fetchone() + return _row_to_budget(row) if row else None + + async def list_budgets_for_user(self, user_id: int) -> list[Budget]: + cursor = await self._db.conn.execute( + """ + SELECT * FROM budgets + WHERE user_id = ? + ORDER BY is_active DESC, id DESC + """, + (user_id,), + ) + rows = await cursor.fetchall() + return [_row_to_budget(row) for row in rows] + + async def resolve_budget( + self, + user_id: int, + budget_id: int | None = None, + *, + require_active: bool = False, + ) -> Budget | None: + if budget_id is not None: + budget = await self.get_budget_for_user(budget_id, user_id) + if budget is None: + return None + if require_active and not budget.is_active: + raise ValueError("Бюджет неактивен — включи его или выбери другой") + return budget + + selected_id = await self.get_selected_budget_id(user_id) + if selected_id is not None: + selected = await self.get_budget_for_user(selected_id, user_id) + if selected is not None: + if not require_active or selected.is_active: + return selected + + cursor = await self._db.conn.execute( + """ + SELECT * FROM budgets + WHERE user_id = ? + ORDER BY is_active DESC, id DESC + LIMIT 1 + """, + (user_id,), + ) + row = await cursor.fetchone() + if row is None: + return None + budget = _row_to_budget(row) + if require_active and not budget.is_active: + raise ValueError("Нет активного бюджета — создай или включи существующий") + return budget + + async def list_all_budgets(self) -> list[Budget]: + cursor = await self._db.conn.execute( + """ + SELECT * FROM budgets + ORDER BY user_id ASC, is_active DESC, id DESC + """ + ) + rows = await cursor.fetchall() + return [_row_to_budget(row) for row in rows] + + async def add_expense( + self, + user_id: int, + budget_id: int, + amount: float, + note: str | None = None, + spent_at: date | None = None, + ) -> Expense: + await self.ensure_user(user_id) + spent = spent_at or date.today() + cursor = await self._db.conn.execute( + """ + INSERT INTO expenses(user_id, budget_id, amount, note, spent_at) + VALUES (?, ?, ?, ?, ?) + RETURNING * + """, + (user_id, budget_id, amount, note, spent.isoformat()), + ) + row = await cursor.fetchone() + await self._db.conn.commit() + if row is None: + raise RuntimeError("Failed to insert expense") + return _row_to_expense(row) + + async def delete_last_expense( + self, + user_id: int, + budget_id: int | None = None, + ) -> Expense | None: + if budget_id is None: + cursor = await self._db.conn.execute( + """ + SELECT * FROM expenses + WHERE user_id = ? + ORDER BY id DESC + LIMIT 1 + """, + (user_id,), + ) + else: + cursor = await self._db.conn.execute( + """ + SELECT * FROM expenses + WHERE user_id = ? AND budget_id = ? + ORDER BY id DESC + LIMIT 1 + """, + (user_id, budget_id), + ) + row = await cursor.fetchone() + if row is None: + return None + await self._db.conn.execute("DELETE FROM expenses WHERE id = ?", (row["id"],)) + await self._db.conn.commit() + return _row_to_expense(row) + + async def spent_on_date( + self, + budget_id: int, + day: date, + ) -> float: + cursor = await self._db.conn.execute( + """ + SELECT COALESCE(SUM(amount), 0) AS total + FROM expenses + WHERE budget_id = ? AND spent_at = ? + """, + (budget_id, day.isoformat()), + ) + row = await cursor.fetchone() + return float(row["total"]) if row else 0.0 + + async def list_expenses_on_date( + self, + budget_id: int, + day: date, + ) -> list[Expense]: + cursor = await self._db.conn.execute( + """ + SELECT * FROM expenses + WHERE budget_id = ? AND spent_at = ? + ORDER BY id ASC + """, + (budget_id, day.isoformat()), + ) + rows = await cursor.fetchall() + return [_row_to_expense(row) for row in rows] + + async def count_expenses_for_budget(self, budget_id: int) -> int: + cursor = await self._db.conn.execute( + "SELECT COUNT(*) AS cnt FROM expenses WHERE budget_id = ?", + (budget_id,), + ) + row = await cursor.fetchone() + return int(row["cnt"]) if row else 0 + + async def sum_expenses_for_budget(self, budget_id: int) -> float: + cursor = await self._db.conn.execute( + """ + SELECT COALESCE(SUM(amount), 0) AS total + FROM expenses + WHERE budget_id = ? + """, + (budget_id,), + ) + row = await cursor.fetchone() + return float(row["total"]) if row else 0.0 + + async def list_expenses_for_budget( + self, + budget_id: int, + *, + limit: int, + offset: int, + ) -> list[Expense]: + cursor = await self._db.conn.execute( + """ + SELECT * FROM expenses + WHERE budget_id = ? + ORDER BY spent_at DESC, id DESC + LIMIT ? OFFSET ? + """, + (budget_id, limit, offset), + ) + rows = await cursor.fetchall() + return [_row_to_expense(row) for row in rows] + + async def clear_expenses_for_budget_id(self, budget_id: int) -> None: + await self._db.conn.execute( + "DELETE FROM expenses WHERE budget_id = ?", + (budget_id,), + ) + await self._db.conn.commit() diff --git a/bot/handlers/__init__.py b/bot/handlers/__init__.py new file mode 100644 index 0000000..0e632e1 --- /dev/null +++ b/bot/handlers/__init__.py @@ -0,0 +1 @@ +# Package marker diff --git a/bot/handlers/budget.py b/bot/handlers/budget.py new file mode 100644 index 0000000..3482067 --- /dev/null +++ b/bot/handlers/budget.py @@ -0,0 +1,781 @@ +from __future__ import annotations + +from datetime import date + +from aiogram import F, Router +from aiogram.filters import Command, CommandObject, CommandStart, StateFilter +from aiogram.fsm.context import FSMContext +from aiogram.types import ( + CallbackQuery, + InlineKeyboardButton, + InlineKeyboardMarkup, + KeyboardButton, + Message, + ReplyKeyboardMarkup, + ReplyKeyboardRemove, +) + +from bot.clients.budget_api import BudgetApiError +from bot.handlers.yandex_gate import YandexLoginGateMiddleware +from bot.handlers.states import BudgetSetup, DatedExpense +from bot.services.budget import BudgetService, PeriodExpensesPage +from bot.services.parsing import ( + format_date, + format_money, + format_status, + parse_amount, + parse_end_date, + parse_expense_message, + parse_spent_date, +) + +router = Router() +router.message.middleware(YandexLoginGateMiddleware()) +router.callback_query.middleware(YandexLoginGateMiddleware()) + +HELP_TEXT = """\ +Я помогаю дотянуть до зарплаты без сюрпризов. + +Команды: +/budget — новый бюджет (имя · сумма · дата) +/budgets — список бюджетов, выбрать / вкл-выкл / удалить +/status — сколько можно тратить сегодня +/today — траты за сегодня +/history — траты за весь период (постранично) +/day 12.09 — траты за дату +/spend — трата за другую дату (диалог) +/undo — отменить последнюю трату +/cancel — отменить текущий диалог +/help — эта справка + +Быстрый ввод трат: +• 250 +• 250 кофе +• кофе 250 +• за 12.09 250 кофе +• 250 кофе за 12.09 +• 12.09 250 кофе +""" + +MENU_STATUS = "📊 Статус" +MENU_TODAY = "🧾 Сегодня" +MENU_HISTORY = "📒 Период" +MENU_DATED = "📅 За дату" +MENU_UNDO = "↩️ Отмена траты" +MENU_BUDGET = "💰 Новый бюджет" +MENU_BUDGETS = "🗂 Бюджеты" + +HISTORY_CB_PREFIX = "hist:" +BUDGET_SELECT_PREFIX = "bsel:" +BUDGET_TOGGLE_PREFIX = "btgl:" +BUDGET_DELETE_PREFIX = "bdel:" + + +def main_keyboard() -> ReplyKeyboardMarkup: + return ReplyKeyboardMarkup( + keyboard=[ + [KeyboardButton(text=MENU_STATUS), KeyboardButton(text=MENU_TODAY)], + [KeyboardButton(text=MENU_HISTORY), KeyboardButton(text=MENU_DATED)], + [KeyboardButton(text=MENU_BUDGETS), KeyboardButton(text=MENU_BUDGET)], + [KeyboardButton(text=MENU_UNDO)], + ], + resize_keyboard=True, + ) + + +def format_period_page(page_data: PeriodExpensesPage) -> str: + budget = page_data.budget + header = ( + f"📒 {budget.name}\n" + f"Траты {format_date(budget.start_date)}–{format_date(budget.end_date)}\n" + f"Всего записей: {page_data.total_count} · " + f"сумма: {format_money(page_data.total_sum)}\n" + f"Страница {page_data.page + 1}/{page_data.total_pages}" + ) + if not page_data.items: + return header + "\n\nПока нет трат за период." + + lines = [] + for item in page_data.items: + note = f" — {item.note}" if item.note else "" + lines.append( + f"• {format_date(item.spent_at)} · {format_money(item.amount)}{note}" + ) + return header + "\n\n" + "\n".join(lines) + + +def format_budgets_list(items) -> str: + if not items: + return "Бюджетов пока нет. Создай: /budget" + lines = ["Твои бюджеты:"] + for status in items: + b = status.budget + marks = [] + if status.selected: + marks.append("текущий") + marks.append("активен" if b.is_active else "выкл") + mark = ", ".join(marks) + lines.append( + f"• #{b.id} {b.name} — {format_money(b.total_amount)} " + f"до {format_date(b.end_date)} ({mark})" + ) + lines.append("\nКнопки: выбрать · вкл/выкл · удалить.") + return "\n".join(lines) + + +def budgets_keyboard(items) -> InlineKeyboardMarkup | None: + if not items: + return None + rows: list[list[InlineKeyboardButton]] = [] + for status in items: + b = status.budget + select_label = f"{'✓ ' if status.selected else ''}{b.name}"[:28] + toggle_label = "Выкл" if b.is_active else "Вкл" + rows.append( + [ + InlineKeyboardButton( + text=select_label, + callback_data=f"{BUDGET_SELECT_PREFIX}{b.id}", + ), + InlineKeyboardButton( + text=toggle_label, + callback_data=f"{BUDGET_TOGGLE_PREFIX}{b.id}", + ), + InlineKeyboardButton( + text="🗑", + callback_data=f"{BUDGET_DELETE_PREFIX}{b.id}", + ), + ] + ) + return InlineKeyboardMarkup(inline_keyboard=rows) + + +def history_keyboard(page_data: PeriodExpensesPage) -> InlineKeyboardMarkup | None: + if page_data.total_pages <= 1: + return None + + buttons: list[InlineKeyboardButton] = [] + if page_data.page > 0: + buttons.append( + InlineKeyboardButton( + text="‹ Назад", + callback_data=f"{HISTORY_CB_PREFIX}{page_data.page - 1}", + ) + ) + buttons.append( + InlineKeyboardButton( + text=f"{page_data.page + 1}/{page_data.total_pages}", + callback_data=f"{HISTORY_CB_PREFIX}nop", + ) + ) + if page_data.page + 1 < page_data.total_pages: + buttons.append( + InlineKeyboardButton( + text="Вперёд ›", + callback_data=f"{HISTORY_CB_PREFIX}{page_data.page + 1}", + ) + ) + return InlineKeyboardMarkup(inline_keyboard=[buttons]) + + +async def _reply_expense_saved( + message: Message, + *, + amount: float, + note: str | None, + spent_at: date | None, + status, +) -> None: + note_part = f" ({note})" if note else "" + date_part = f" за {format_date(spent_at)}" if spent_at else "" + await message.answer( + f"Записал {format_money(amount)}{note_part}{date_part}.\n\n" + f"{format_status(status)}", + reply_markup=main_keyboard(), + ) + + +async def _save_expense( + message: Message, + budget_service: BudgetService, + *, + amount: float, + note: str | None, + spent_at: date | None, + state: FSMContext | None = None, +) -> bool: + try: + status = await budget_service.add_expense( + user_id=message.from_user.id, + amount=amount, + note=note, + spent_at=spent_at, + ) + except ValueError as exc: + await message.answer(str(exc), reply_markup=main_keyboard()) + if state is not None: + await state.clear() + return False + except BudgetApiError as exc: + await message.answer( + f"API недоступен: {exc}", + reply_markup=main_keyboard(), + ) + if state is not None: + await state.clear() + return False + + if state is not None: + await state.clear() + await _reply_expense_saved( + message, + amount=amount, + note=note, + spent_at=spent_at, + status=status, + ) + return True + + +# --- Global commands / menu (always win over FSM) --- + + +@router.message(CommandStart()) +async def cmd_start(message: Message, state: FSMContext) -> None: + await state.clear() + await message.answer( + "Привет! Я бот «от зарплаты до зарплаты».\n\n" + "1) Задай бюджет: /budget\n" + "2) Пиши траты: 250 или кофе 250\n" + "3) За другую дату: за 12.09 250 кофе\n" + "4) Смотри лимит: /status", + reply_markup=main_keyboard(), + ) + + +@router.message(Command("help")) +async def cmd_help(message: Message, state: FSMContext) -> None: + await state.clear() + await message.answer(HELP_TEXT, reply_markup=main_keyboard()) + + +@router.message(Command("cancel")) +@router.message(F.text.casefold() == "отмена") +async def cmd_cancel(message: Message, state: FSMContext) -> None: + current = await state.get_state() + if current is None: + await message.answer("Нечего отменять.", reply_markup=main_keyboard()) + return + await state.clear() + await message.answer("Ок, отменил.", reply_markup=main_keyboard()) + + +@router.message(Command("status")) +@router.message(F.text == MENU_STATUS) +async def cmd_status( + message: Message, + state: FSMContext, + budget_service: BudgetService, +) -> None: + await state.clear() + try: + status = await budget_service.get_status(message.from_user.id) + except ValueError as exc: + await message.answer(str(exc), reply_markup=main_keyboard()) + return + await message.answer(format_status(status), reply_markup=main_keyboard()) + + +@router.message(Command("today")) +@router.message(F.text == MENU_TODAY) +async def cmd_today( + message: Message, + state: FSMContext, + budget_service: BudgetService, +) -> None: + await state.clear() + try: + status = await budget_service.get_status(message.from_user.id) + except ValueError as exc: + await message.answer(str(exc), reply_markup=main_keyboard()) + return + + expenses = await budget_service.today_expenses(message.from_user.id) + if not expenses: + body = "Сегодня трат пока нет." + else: + lines = [] + for item in expenses: + note = f" — {item.note}" if item.note else "" + lines.append(f"• {format_money(item.amount)}{note}") + body = "Траты сегодня:\n" + "\n".join(lines) + + await message.answer( + f"{body}\n\n{format_status(status)}", + reply_markup=main_keyboard(), + ) + + +@router.message(Command("history")) +@router.message(F.text == MENU_HISTORY) +async def cmd_history( + message: Message, + state: FSMContext, + budget_service: BudgetService, +) -> None: + await state.clear() + try: + page_data = await budget_service.get_period_expenses_page( + message.from_user.id, + page=0, + ) + except ValueError as exc: + await message.answer(str(exc), reply_markup=main_keyboard()) + return + + await message.answer( + format_period_page(page_data), + reply_markup=history_keyboard(page_data) or main_keyboard(), + ) + + +@router.callback_query(F.data.startswith(HISTORY_CB_PREFIX)) +async def cb_history_page( + callback: CallbackQuery, + budget_service: BudgetService, +) -> None: + raw = (callback.data or "")[len(HISTORY_CB_PREFIX) :] + if raw == "nop": + await callback.answer() + return + + try: + page = int(raw) + except ValueError: + await callback.answer("Некорректная страница", show_alert=True) + return + + try: + page_data = await budget_service.get_period_expenses_page( + callback.from_user.id, + page=page, + ) + except ValueError as exc: + await callback.answer(str(exc), show_alert=True) + return + + text = format_period_page(page_data) + markup = history_keyboard(page_data) + if callback.message: + await callback.message.edit_text(text, reply_markup=markup) + await callback.answer() + + +@router.message(Command("day")) +async def cmd_day( + message: Message, + command: CommandObject, + state: FSMContext, + budget_service: BudgetService, +) -> None: + await state.clear() + args = (command.args or "").strip() + if not args: + await message.answer( + "Укажи дату: /day 12.09\n" + "Или список трат за сегодня: /today", + reply_markup=main_keyboard(), + ) + return + + try: + day = parse_spent_date(args) + except ValueError as exc: + await message.answer(str(exc), reply_markup=main_keyboard()) + return + + try: + status = await budget_service.get_status(message.from_user.id) + except ValueError as exc: + await message.answer(str(exc), reply_markup=main_keyboard()) + return + + expenses = await budget_service.expenses_on_date(message.from_user.id, day) + if not expenses: + body = f"За {format_date(day)} трат нет." + else: + lines = [] + for item in expenses: + note = f" — {item.note}" if item.note else "" + lines.append(f"• {format_money(item.amount)}{note}") + body = f"Траты за {format_date(day)}:\n" + "\n".join(lines) + + await message.answer( + f"{body}\n\n{format_status(status)}", + reply_markup=main_keyboard(), + ) + + +@router.message(Command("undo")) +@router.message(F.text == MENU_UNDO) +async def cmd_undo( + message: Message, + state: FSMContext, + budget_service: BudgetService, +) -> None: + await state.clear() + status, amount = await budget_service.undo_last_expense(message.from_user.id) + if amount is None: + await message.answer("Нечего отменять.", reply_markup=main_keyboard()) + return + + text = f"Удалил последнюю трату: {format_money(amount)}." + if status is not None: + text += f"\n\n{format_status(status)}" + await message.answer(text, reply_markup=main_keyboard()) + + +@router.message(Command("budget")) +@router.message(F.text == MENU_BUDGET) +async def cmd_budget(message: Message, state: FSMContext) -> None: + await state.set_state(BudgetSetup.waiting_name) + await message.answer( + "Новый бюджет. Как назвать? (например: Зарплата, Отпуск)\n" + "Или «-» чтобы оставить «Бюджет».\n" + "Отмена: /cancel", + reply_markup=ReplyKeyboardRemove(), + ) + + +@router.message(Command("budgets")) +@router.message(F.text == MENU_BUDGETS) +async def cmd_budgets( + message: Message, + state: FSMContext, + budget_service: BudgetService, +) -> None: + await state.clear() + try: + items = await budget_service.list_user_statuses(message.from_user.id) + except (ValueError, BudgetApiError) as exc: + await message.answer(f"Не удалось загрузить: {exc}", reply_markup=main_keyboard()) + return + await message.answer( + format_budgets_list(items), + reply_markup=budgets_keyboard(items) or main_keyboard(), + ) + + +@router.callback_query(F.data.startswith(BUDGET_SELECT_PREFIX)) +async def cb_budget_select( + callback: CallbackQuery, + budget_service: BudgetService, +) -> None: + raw = (callback.data or "")[len(BUDGET_SELECT_PREFIX) :] + try: + budget_id = int(raw) + except ValueError: + await callback.answer("Некорректный id", show_alert=True) + return + try: + status = await budget_service.select_budget(callback.from_user.id, budget_id) + items = await budget_service.list_user_statuses(callback.from_user.id) + except ValueError as exc: + await callback.answer(str(exc), show_alert=True) + return + if callback.message: + await callback.message.edit_text( + format_budgets_list(items) + f"\n\nТекущий: {status.budget.name}", + reply_markup=budgets_keyboard(items), + ) + await callback.answer(f"Выбран: {status.budget.name}") + + +@router.callback_query(F.data.startswith(BUDGET_TOGGLE_PREFIX)) +async def cb_budget_toggle( + callback: CallbackQuery, + budget_service: BudgetService, +) -> None: + raw = (callback.data or "")[len(BUDGET_TOGGLE_PREFIX) :] + try: + budget_id = int(raw) + except ValueError: + await callback.answer("Некорректный id", show_alert=True) + return + try: + current = await budget_service.get_status( + callback.from_user.id, + budget_id=budget_id, + ) + status = await budget_service.set_budget_active( + callback.from_user.id, + budget_id, + not current.budget.is_active, + ) + items = await budget_service.list_user_statuses(callback.from_user.id) + except ValueError as exc: + await callback.answer(str(exc), show_alert=True) + return + if callback.message: + await callback.message.edit_text( + format_budgets_list(items), + reply_markup=budgets_keyboard(items), + ) + state_label = "включён" if status.budget.is_active else "выключен" + await callback.answer(f"{status.budget.name}: {state_label}") + + +@router.callback_query(F.data.startswith(BUDGET_DELETE_PREFIX)) +async def cb_budget_delete( + callback: CallbackQuery, + budget_service: BudgetService, +) -> None: + raw = (callback.data or "")[len(BUDGET_DELETE_PREFIX) :] + try: + budget_id = int(raw) + except ValueError: + await callback.answer("Некорректный id", show_alert=True) + return + try: + current = await budget_service.get_status( + callback.from_user.id, + budget_id=budget_id, + ) + name = current.budget.name + await budget_service.delete_budget(callback.from_user.id, budget_id) + items = await budget_service.list_user_statuses(callback.from_user.id) + except (ValueError, BudgetApiError) as exc: + await callback.answer(str(exc), show_alert=True) + return + if callback.message: + await callback.message.edit_text( + format_budgets_list(items), + reply_markup=budgets_keyboard(items) or None, + ) + await callback.answer(f"Удалён: {name}") + + +@router.message(Command("spend")) +@router.message(F.text == MENU_DATED) +async def cmd_spend_dated(message: Message, state: FSMContext) -> None: + await state.set_state(DatedExpense.waiting_date) + await message.answer( + "Трата за дату — одним сообщением или по шагам.\n\n" + "Сразу: 12.09 250 кб\n" + "Или только дата: 12.09\n" + "Отмена: /cancel", + reply_markup=ReplyKeyboardRemove(), + ) + + +# --- FSM: budget --- + + +@router.message(BudgetSetup.waiting_name) +async def budget_name(message: Message, state: FSMContext) -> None: + raw = (message.text or "").strip() + name = "Бюджет" if raw in {"", "-", "—"} else raw[:64] + await state.update_data(name=name) + await state.set_state(BudgetSetup.waiting_amount) + await message.answer( + f"Название: {name}.\n" + "Сколько денег в этом бюджете? (например: 25000)" + ) + + +@router.message(BudgetSetup.waiting_amount) +async def budget_amount(message: Message, state: FSMContext) -> None: + try: + amount = parse_amount(message.text or "") + except ValueError as exc: + await message.answer(f"{exc}\nПопробуй ещё раз, например: 25000") + return + + await state.update_data(amount=amount) + await state.set_state(BudgetSetup.waiting_end_date) + await message.answer( + "До какой даты нужно протянуть?\n" + "Форматы: 25.09 · 25.09.2026 · 2026-09-25" + ) + + +@router.message(BudgetSetup.waiting_end_date) +async def budget_end_date( + message: Message, + state: FSMContext, + budget_service: BudgetService, +) -> None: + try: + end_date = parse_end_date(message.text or "") + except ValueError as exc: + await message.answer(f"{exc}\nПример: 25.09.2026") + return + + data = await state.get_data() + amount = float(data["amount"]) + name = str(data.get("name") or "Бюджет") + + try: + status = await budget_service.create_budget( + user_id=message.from_user.id, + total_amount=amount, + end_date=end_date, + name=name, + ) + except ValueError as exc: + await message.answer(str(exc)) + return + except BudgetApiError as exc: + await message.answer(f"API недоступен: {exc}") + return + + await state.clear() + await message.answer( + f"Бюджет «{name}» создан: {format_money(amount)} до {format_date(end_date)}.\n\n" + f"{format_status(status)}", + reply_markup=main_keyboard(), + ) + + +# --- FSM: dated expense --- + + +@router.message(DatedExpense.waiting_date) +async def dated_expense_date( + message: Message, + state: FSMContext, + budget_service: BudgetService, +) -> None: + text = (message.text or "").strip() + if not text: + await message.answer("Введи дату, например: 12.09") + return + + # One-shot: "12.09 250 кб" / "за 12.09 250 кб" + try: + amount, note, spent_at = parse_expense_message(text) + except ValueError: + amount, note, spent_at = None, None, None + + if amount is not None and spent_at is not None: + await _save_expense( + message, + budget_service, + amount=amount, + note=note, + spent_at=spent_at, + state=state, + ) + return + + # Date only, or "12.09 кб" (date + note without amount) + tokens = text.split() + try: + spent_at = parse_spent_date(tokens[0].rstrip(":")) + except ValueError: + await message.answer( + "Не понял дату.\n" + "Примеры: 12.09 или 12.09 250 кб\n" + "Отмена: /cancel" + ) + return + + if spent_at > date.today(): + await message.answer("Нельзя добавить трату на будущую дату. Введи другую:") + return + + rest = " ".join(tokens[1:]).strip() + if rest: + try: + amount, note, nested = parse_expense_message(rest) + except ValueError: + # "12.09 кб" → дата есть, суммы нет: запомним заметку и спросим сумму + await state.update_data(spent_at=spent_at.isoformat(), note_hint=rest) + await state.set_state(DatedExpense.waiting_expense) + await message.answer( + f"Дата: {format_date(spent_at)}, заметка: {rest}.\n" + "Теперь сумму, например: 250" + ) + return + + if nested is not None: + spent_at = nested + await _save_expense( + message, + budget_service, + amount=amount, + note=note, + spent_at=spent_at, + state=state, + ) + return + + await state.update_data(spent_at=spent_at.isoformat(), note_hint=None) + await state.set_state(DatedExpense.waiting_expense) + await message.answer( + f"Дата: {format_date(spent_at)}.\n" + "Теперь сумма (и комментарий): 250 или 250 кб" + ) + + +@router.message(DatedExpense.waiting_expense) +async def dated_expense_amount( + message: Message, + state: FSMContext, + budget_service: BudgetService, +) -> None: + data = await state.get_data() + spent_at = date.fromisoformat(data["spent_at"]) + note_hint = data.get("note_hint") + + try: + amount, note, nested_date = parse_expense_message(message.text or "") + except ValueError as exc: + await message.answer(f"{exc}") + return + + if nested_date is not None: + spent_at = nested_date + if note is None and note_hint: + note = note_hint + + await _save_expense( + message, + budget_service, + amount=amount, + note=note, + spent_at=spent_at, + state=state, + ) + + +# --- Free-text expense (no active dialog) --- + + +@router.message(StateFilter(None), F.text) +async def add_expense_from_text( + message: Message, + budget_service: BudgetService, +) -> None: + text = (message.text or "").strip() + if text.startswith("/"): + return + + try: + amount, note, spent_at = parse_expense_message(text) + except ValueError: + await message.answer( + "Не понял. Примеры:\n" + "• 250 / 250 кофе / кофе 250\n" + "• за 12.09 250 кофе\n" + "• /spend — диалог за дату" + ) + return + + await _save_expense( + message, + budget_service, + amount=amount, + note=note, + spent_at=spent_at, + ) diff --git a/bot/handlers/states.py b/bot/handlers/states.py new file mode 100644 index 0000000..6b6f988 --- /dev/null +++ b/bot/handlers/states.py @@ -0,0 +1,12 @@ +from aiogram.fsm.state import State, StatesGroup + + +class BudgetSetup(StatesGroup): + waiting_name = State() + waiting_amount = State() + waiting_end_date = State() + + +class DatedExpense(StatesGroup): + waiting_date = State() + waiting_expense = State() diff --git a/bot/handlers/yandex_gate.py b/bot/handlers/yandex_gate.py new file mode 100644 index 0000000..f75e584 --- /dev/null +++ b/bot/handlers/yandex_gate.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from typing import Any, Awaitable, Callable + +from aiogram import BaseMiddleware +from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message, TelegramObject + +from bot.clients.budget_api import YandexLoginRequired + +YANDEX_REQUIRED_TEXT = """\ +Чтобы пользоваться ботом, войдите через Яндекс. + +Так Telegram, сайт и приложение — один аккаунт, и бюджеты не потеряются. + +Откройте ссылку, примите документы и нажмите «Войти через Яндекс». Потом вернитесь в бот и напишите /start. +""" + + +def yandex_login_keyboard(login_url: str) -> InlineKeyboardMarkup | None: + url = (login_url or "").strip() + if not url.startswith(("https://", "http://")): + return None + return InlineKeyboardMarkup( + inline_keyboard=[[InlineKeyboardButton(text="Войти через Яндекс", url=url)]] + ) + + +class YandexLoginGateMiddleware(BaseMiddleware): + async def __call__( + self, + handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], + event: TelegramObject, + data: dict[str, Any], + ) -> Any: + user = getattr(event, "from_user", None) + service = data.get("budget_service") + ensure = getattr(service, "ensure_yandex_login", None) + if user is None or not callable(ensure): + return await handler(event, data) + + try: + await ensure(user.id) + except YandexLoginRequired as exc: + markup = yandex_login_keyboard(exc.login_url) + if isinstance(event, Message): + await event.answer(YANDEX_REQUIRED_TEXT, reply_markup=markup) + elif isinstance(event, CallbackQuery): + await event.answer() + if event.message: + await event.message.answer(YANDEX_REQUIRED_TEXT, reply_markup=markup) + return None + + return await handler(event, data) diff --git a/bot/main.py b/bot/main.py new file mode 100644 index 0000000..08c9051 --- /dev/null +++ b/bot/main.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import asyncio +import logging + +from aiogram import Bot, Dispatcher +from aiogram.client.session.aiohttp import AiohttpSession +from aiogram.fsm.storage.memory import MemoryStorage + +from bot.clients.budget_api import BudgetApiClient +from bot.config import settings +from bot.handlers.budget import router as budget_router + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", +) +logger = logging.getLogger(__name__) + + +def create_bot() -> Bot: + if settings.proxy_url: + logger.info("Using proxy for Telegram API: %s", settings.proxy_url) + session = AiohttpSession(proxy=settings.proxy_url) + else: + session = AiohttpSession() + return Bot(token=settings.bot_token, session=session) + + +async def main() -> None: + budget_service = BudgetApiClient( + base_url=settings.api_base_url, + api_token=settings.api_token, + ) + await budget_service.start() + + bot = create_bot() + dp = Dispatcher(storage=MemoryStorage()) + dp["budget_service"] = budget_service + dp.include_router(budget_router) + + try: + logger.info("Bot started (API %s)", settings.api_base_url) + await dp.start_polling(bot) + finally: + await budget_service.close() + await bot.session.close() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/bot/services/__init__.py b/bot/services/__init__.py new file mode 100644 index 0000000..0e632e1 --- /dev/null +++ b/bot/services/__init__.py @@ -0,0 +1 @@ +# Package marker diff --git a/bot/services/budget.py b/bot/services/budget.py new file mode 100644 index 0000000..4a724bf --- /dev/null +++ b/bot/services/budget.py @@ -0,0 +1,397 @@ +from __future__ import annotations + +from dataclasses import dataclass +from datetime import date +from math import ceil + +from bot.db.repository import Budget, BudgetRepository, Expense + +PERIOD_PAGE_SIZE = 8 + + +@dataclass(frozen=True) +class BudgetStatus: + budget: Budget + today: date + days_left: int + total_spent: float + remaining: float + daily_limit: float + spent_today: float + remaining_today: float + is_over_daily: bool + is_over_budget: bool + is_expired: bool + selected: bool = False + + +@dataclass(frozen=True) +class PeriodExpensesPage: + budget: Budget + page: int + total_pages: int + total_count: int + total_sum: float + page_size: int + items: list[Expense] + + +class BudgetService: + def __init__(self, repo: BudgetRepository) -> None: + self._repo = repo + + async def list_user_statuses(self, user_id: int) -> list[BudgetStatus]: + budgets = await self._repo.list_budgets_for_user(user_id) + selected_id = await self._repo.get_selected_budget_id(user_id) + return [ + await self.get_status_for_budget( + budget, + selected=(budget.id == selected_id), + ) + for budget in budgets + ] + + async def create_budget( + self, + user_id: int, + total_amount: float, + end_date: date, + *, + name: str = "Бюджет", + start_date: date | None = None, + is_active: bool = True, + select: bool = True, + ) -> BudgetStatus: + start = start_date or date.today() + if end_date < start: + raise ValueError("Дата окончания не может быть раньше сегодняшнего дня") + if total_amount <= 0: + raise ValueError("Сумма бюджета должна быть больше нуля") + + budget = await self._repo.create_budget( + user_id, + name=name, + total_amount=total_amount, + start_date=start, + end_date=end_date, + is_active=is_active, + select=select, + ) + return await self.get_status_for_budget(budget, selected=select) + + async def update_budget( + self, + user_id: int, + budget_id: int, + *, + name: str | None = None, + total_amount: float | None = None, + end_date: date | None = None, + start_date: date | None = None, + reset_expenses: bool = False, + ) -> BudgetStatus: + existing = await self._repo.get_budget_for_user(budget_id, user_id) + if existing is None: + raise ValueError("Бюджет не найден") + + next_end = end_date if end_date is not None else existing.end_date + next_start = start_date if start_date is not None else existing.start_date + if next_end < next_start: + raise ValueError("Дата окончания не может быть раньше даты начала") + if total_amount is not None and total_amount <= 0: + raise ValueError("Сумма бюджета должна быть больше нуля") + + if reset_expenses: + await self._repo.clear_expenses_for_budget_id(budget_id) + + budget = await self._repo.update_budget( + budget_id, + user_id, + name=name, + total_amount=total_amount, + start_date=start_date, + end_date=end_date, + ) + selected_id = await self._repo.get_selected_budget_id(user_id) + return await self.get_status_for_budget( + budget, + selected=(budget.id == selected_id), + ) + + async def set_budget_active( + self, + user_id: int, + budget_id: int, + is_active: bool, + ) -> BudgetStatus: + budget = await self._repo.set_budget_active(budget_id, user_id, is_active) + selected_id = await self._repo.get_selected_budget_id(user_id) + if not is_active and selected_id == budget_id: + # Prefer another active budget as selected. + others = await self._repo.list_budgets_for_user(user_id) + next_selected = next((b.id for b in others if b.is_active), None) + await self._repo.set_selected_budget_id(user_id, next_selected) + selected_id = next_selected + return await self.get_status_for_budget( + budget, + selected=(budget.id == selected_id), + ) + + async def select_budget(self, user_id: int, budget_id: int) -> BudgetStatus: + budget = await self._repo.get_budget_for_user(budget_id, user_id) + if budget is None: + raise ValueError("Бюджет не найден") + await self._repo.set_selected_budget_id(user_id, budget_id) + return await self.get_status_for_budget(budget, selected=True) + + async def set_budget( + self, + user_id: int, + total_amount: float, + end_date: date, + start_date: date | None = None, + reset_expenses: bool = True, + name: str | None = None, + budget_id: int | None = None, + ) -> BudgetStatus: + """Backward-compatible: update selected/specified budget or create new.""" + if budget_id is not None: + return await self.update_budget( + user_id, + budget_id, + name=name, + total_amount=total_amount, + end_date=end_date, + start_date=start_date, + reset_expenses=reset_expenses, + ) + + current = await self._repo.resolve_budget(user_id) + if current is None: + return await self.create_budget( + user_id, + total_amount, + end_date, + name=name or "Бюджет", + start_date=start_date, + ) + return await self.update_budget( + user_id, + current.id, + name=name, + total_amount=total_amount, + end_date=end_date, + start_date=start_date, + reset_expenses=reset_expenses, + ) + + async def add_expense( + self, + user_id: int, + amount: float, + note: str | None = None, + spent_at: date | None = None, + budget_id: int | None = None, + ) -> BudgetStatus: + if amount <= 0: + raise ValueError("Сумма траты должна быть больше нуля") + budget = await self._repo.resolve_budget( + user_id, + budget_id, + require_active=True, + ) + if budget is None: + raise ValueError("Сначала задай бюджет: /budget") + + day = spent_at or date.today() + if day > date.today(): + raise ValueError("Нельзя добавить трату на будущую дату") + if day < budget.start_date or day > budget.end_date: + raise ValueError( + "Дата вне периода бюджета " + f"({budget.start_date.strftime('%d.%m.%Y')}–" + f"{budget.end_date.strftime('%d.%m.%Y')})" + ) + + await self._repo.add_expense( + user_id, + budget.id, + amount=amount, + note=note, + spent_at=day, + ) + selected_id = await self._repo.get_selected_budget_id(user_id) + return await self.get_status_for_budget( + budget, + selected=(budget.id == selected_id), + ) + + async def expenses_on_date( + self, + user_id: int, + day: date, + budget_id: int | None = None, + ) -> list[Expense]: + budget = await self._repo.resolve_budget(user_id, budget_id) + if budget is None: + raise ValueError("Сначала задай бюджет: /budget") + return await self._repo.list_expenses_on_date(budget.id, day) + + async def today_expenses( + self, + user_id: int, + today: date | None = None, + budget_id: int | None = None, + ) -> list[Expense]: + day = today or date.today() + return await self.expenses_on_date(user_id, day, budget_id=budget_id) + + async def get_expenses_on_date_page( + self, + user_id: int, + day: date, + page: int = 0, + page_size: int = PERIOD_PAGE_SIZE, + budget_id: int | None = None, + ) -> PeriodExpensesPage: + budget = await self._repo.resolve_budget(user_id, budget_id) + if budget is None: + raise ValueError("Сначала задай бюджет: /budget") + + if page < 0: + page = 0 + if page_size < 1: + page_size = PERIOD_PAGE_SIZE + + items_all = await self._repo.list_expenses_on_date(budget.id, day) + total_count = len(items_all) + total_sum = sum(item.amount for item in items_all) + total_pages = max(1, ceil(total_count / page_size)) if total_count else 1 + if page >= total_pages: + page = total_pages - 1 + + start = page * page_size + items = items_all[start : start + page_size] + return PeriodExpensesPage( + budget=budget, + page=page, + total_pages=total_pages, + total_count=total_count, + total_sum=total_sum, + page_size=page_size, + items=items, + ) + + async def get_period_expenses_page( + self, + user_id: int, + page: int = 0, + page_size: int = PERIOD_PAGE_SIZE, + budget_id: int | None = None, + ) -> PeriodExpensesPage: + budget = await self._repo.resolve_budget(user_id, budget_id) + if budget is None: + raise ValueError("Сначала задай бюджет: /budget") + + if page < 0: + page = 0 + if page_size < 1: + page_size = PERIOD_PAGE_SIZE + + total_count = await self._repo.count_expenses_for_budget(budget.id) + total_sum = await self._repo.sum_expenses_for_budget(budget.id) + total_pages = max(1, ceil(total_count / page_size)) if total_count else 1 + if page >= total_pages: + page = total_pages - 1 + + items = await self._repo.list_expenses_for_budget( + budget.id, + limit=page_size, + offset=page * page_size, + ) + return PeriodExpensesPage( + budget=budget, + page=page, + total_pages=total_pages, + total_count=total_count, + total_sum=total_sum, + page_size=page_size, + items=items, + ) + + async def list_budget_summaries(self) -> list[BudgetStatus]: + budgets = await self._repo.list_all_budgets() + return [await self.get_status_for_budget(budget) for budget in budgets] + + async def undo_last_expense( + self, + user_id: int, + budget_id: int | None = None, + ) -> tuple[BudgetStatus | None, float | None]: + budget = await self._repo.resolve_budget(user_id, budget_id) + target_id = budget.id if budget else None + deleted = await self._repo.delete_last_expense(user_id, target_id) + if deleted is None: + return None, None + status = await self.get_status( + user_id, + budget_id=deleted.budget_id, + ) + return status, deleted.amount + + async def get_status( + self, + user_id: int, + today: date | None = None, + budget_id: int | None = None, + ) -> BudgetStatus: + budget = await self._repo.resolve_budget(user_id, budget_id) + if budget is None: + raise ValueError("Сначала задай бюджет: /budget") + selected_id = await self._repo.get_selected_budget_id(user_id) + return await self.get_status_for_budget( + budget, + today=today, + selected=(budget.id == selected_id), + ) + + async def get_status_for_budget( + self, + budget: Budget, + today: date | None = None, + *, + selected: bool = False, + ) -> BudgetStatus: + day = today or date.today() + total_spent = await self._repo.sum_expenses_for_budget(budget.id) + spent_today = await self._repo.spent_on_date(budget.id, day) + remaining = budget.total_amount - total_spent + + if day > budget.end_date: + days_left = 0 + daily_limit = 0.0 + remaining_today = 0.0 + is_expired = True + else: + days_left = (budget.end_date - day).days + 1 + remaining_at_day_start = remaining + spent_today + daily_limit = ( + remaining_at_day_start / days_left if days_left > 0 else 0.0 + ) + remaining_today = daily_limit - spent_today + is_expired = False + + return BudgetStatus( + budget=budget, + today=day, + days_left=days_left, + total_spent=total_spent, + remaining=remaining, + daily_limit=max(daily_limit, 0.0), + spent_today=spent_today, + remaining_today=remaining_today, + is_over_daily=spent_today > daily_limit and days_left > 0, + is_over_budget=remaining < 0, + is_expired=is_expired, + selected=selected, + ) diff --git a/bot/services/parsing.py b/bot/services/parsing.py new file mode 100644 index 0000000..c87defb --- /dev/null +++ b/bot/services/parsing.py @@ -0,0 +1,213 @@ +from __future__ import annotations + +import re +from datetime import date + +AMOUNT_RE = re.compile( + r"^\s*(?P\d+(?:[.,]\d{1,2})?)\s*(?P.*)?$", + re.UNICODE, +) +NOTE_FIRST_RE = re.compile( + r"^\s*(?P.+?)\s+(?P\d+(?:[.,]\d{1,2})?)\s*$", + re.UNICODE, +) +DATE_RE = re.compile( + r"^(?P\d{1,2})[./](?P\d{1,2})(?:[./](?P\d{2,4}))?$" +) +# Unambiguous date token: ISO, with year, slash, or zero-padded month (12.09) +DATE_TOKEN_RE = re.compile( + r"(?P" + r"\d{4}-\d{2}-\d{2}" + r"|" + r"\d{1,2}[./]\d{1,2}[./]\d{2,4}" + r"|" + r"\d{1,2}/\d{1,2}" + r"|" + r"\d{1,2}\.(?:0[1-9]|1[0-2])" + r")" +) +ZA_DATE_RE = re.compile( + r"(?i)(?:^|\s)за\s+(?P" + r"\d{4}-\d{2}-\d{2}" + r"|" + r"\d{1,2}[./]\d{1,2}(?:[./]\d{2,4})?" + r")" + r"(?=\s|$)" +) + + +def parse_amount(value: str) -> float: + normalized = value.strip().replace(",", ".").replace(" ", "") + amount = float(normalized) + if amount <= 0: + raise ValueError("Сумма должна быть больше нуля") + return round(amount, 2) + + +def parse_spent_date(value: str, today: date | None = None) -> date: + """Parse expense date. Bare DD.MM prefers current/past year (not future).""" + day = today or date.today() + raw = value.strip() + + try: + return date.fromisoformat(raw) + except ValueError: + pass + + match = DATE_RE.match(raw) + if not match: + raise ValueError("Дата в формате ДД.ММ или ДД.ММ.ГГГГ") + + d = int(match.group("d")) + m = int(match.group("m")) + y_raw = match.group("y") + if y_raw is None: + try: + candidate = date(day.year, m, d) + except ValueError as exc: + raise ValueError("Некорректная дата") from exc + if candidate > day: + try: + candidate = date(day.year - 1, m, d) + except ValueError as exc: + raise ValueError("Некорректная дата") from exc + return candidate + + y = int(y_raw) + if y < 100: + y += 2000 + try: + return date(y, m, d) + except ValueError as exc: + raise ValueError("Некорректная дата") from exc + + +def parse_end_date(value: str, today: date | None = None) -> date: + """Parse DD.MM, DD.MM.YYYY, YYYY-MM-DD (for budget end — may roll to next year).""" + day = today or date.today() + raw = value.strip() + + try: + return date.fromisoformat(raw) + except ValueError: + pass + + match = DATE_RE.match(raw) + if not match: + raise ValueError("Дата в формате ДД.ММ или ДД.ММ.ГГГГ") + + d = int(match.group("d")) + m = int(match.group("m")) + y_raw = match.group("y") + if y_raw is None: + y = day.year + candidate = date(y, m, d) + if candidate < day: + candidate = date(y + 1, m, d) + return candidate + + y = int(y_raw) + if y < 100: + y += 2000 + return date(y, m, d) + + +def _parse_amount_and_note(raw: str) -> tuple[float, str | None]: + text = raw.strip() + if not text: + raise ValueError("Не понял трату. Примеры: 250, 250 кофе, кофе 250") + + first_token = text.split()[0] + if re.fullmatch(r"\d+(?:[.,]\d{1,2})?", first_token): + match = AMOUNT_RE.match(text) + if not match: + raise ValueError("Не понял трату. Примеры: 250, 250 кофе, кофе 250") + amount = parse_amount(match.group("amount")) + note = (match.group("note") or "").strip() or None + return amount, note + + match = NOTE_FIRST_RE.match(text) + if match: + amount = parse_amount(match.group("amount")) + note = match.group("note").strip() or None + return amount, note + + raise ValueError("Не понял трату. Примеры: 250, 250 кофе, кофе 250") + + +def parse_expense_message( + text: str, + today: date | None = None, +) -> tuple[float, str | None, date | None]: + """Parse expense text. + + Examples: + - 250 / 250 кофе / кофе 250 + - за 12.09 250 кофе + - 250 кофе за 12.09 + - 12.09 250 кофе + - 250 кофе 12.09 + """ + raw = text.strip() + if not raw: + raise ValueError("Пустое сообщение") + + spent_at: date | None = None + body = raw + + za_match = ZA_DATE_RE.search(raw) + if za_match: + spent_at = parse_spent_date(za_match.group("date"), today=today) + body = (raw[: za_match.start()] + " " + raw[za_match.end() :]).strip() + else: + tokens = raw.split() + if len(tokens) >= 2: + first = tokens[0].rstrip(":") + last = tokens[-1] + if DATE_TOKEN_RE.fullmatch(first): + spent_at = parse_spent_date(first, today=today) + body = " ".join(tokens[1:]) + elif DATE_TOKEN_RE.fullmatch(last): + spent_at = parse_spent_date(last, today=today) + body = " ".join(tokens[:-1]) + + amount, note = _parse_amount_and_note(body) + return amount, note, spent_at + + +def format_money(amount: float, currency: str = "₽") -> str: + sign = "-" if amount < 0 else "" + return f"{sign}{abs(amount):,.2f} {currency}".replace(",", " ") + + +def format_date(value: date) -> str: + return value.strftime("%d.%m.%Y") + + +def format_status(status) -> str: + from bot.services.budget import BudgetStatus + + assert isinstance(status, BudgetStatus) + currency = "₽" + active = "активен" if status.budget.is_active else "неактивен" + selected = " · текущий" if status.selected else "" + lines = [ + f"🏷 {status.budget.name} ({active}{selected})", + f"📅 До {format_date(status.budget.end_date)} · дней осталось: {status.days_left}", + f"💰 Бюджет: {format_money(status.budget.total_amount, currency)}", + f"🧾 Потрачено: {format_money(status.total_spent, currency)}", + f"🛡 Остаток: {format_money(status.remaining, currency)}", + "", + f"📊 Лимит на день: {format_money(status.daily_limit, currency)}", + f"🛒 Сегодня: {format_money(status.spent_today, currency)}", + f"✅ Ещё можно сегодня: {format_money(status.remaining_today, currency)}", + ] + + if status.is_expired: + lines.append("\n⚠️ Период бюджета закончился. Задай новый: /budget") + elif status.is_over_budget: + lines.append("\n🚨 Бюджет уже превышен.") + elif status.is_over_daily: + lines.append("\n⚠️ Сегодняшний лимит превышен — завтра лимит пересчитается по остатку.") + + return "\n".join(lines) diff --git a/context/2026-09-09_02-00-35_paycheck-bot-mvp.md b/context/2026-09-09_02-00-35_paycheck-bot-mvp.md new file mode 100644 index 0000000..7e5a9b4 --- /dev/null +++ b/context/2026-09-09_02-00-35_paycheck-bot-mvp.md @@ -0,0 +1,32 @@ +# 2026-09-09 — Telegram-бот «от зарплаты до зарплаты» + +## Запрос +Сделать бота: ввод суммы и даты «до зарплаты», расчёт дневного лимита, быстрый учёт дневных трат. + +## Решение +С нуля собран Telegram-бот на **aiogram 3** + **SQLite (aiosqlite)**. + +### Структура +- `bot/main.py` — точка входа, polling +- `bot/config.py` — `BOT_TOKEN` из `.env` +- `bot/db/` — схема, репозиторий +- `bot/services/budget.py` — лимит дня от остатка на утро (`(остаток+траты_сегодня)/дней`), модель «утреннего конверта» +- `bot/services/parsing.py` — парсинг сумм/дат/трат +- `bot/handlers/budget.py` — команды и FSM настройки бюджета + +### UX +- `/budget` → сумма → дата +- Трата одним сообщением: `250`, `250 кофе`, `кофе 250` +- `/status`, `/today`, `/undo` +- Reply-клавиатура: Статус / Сегодня / Новый бюджет / Отмена траты + +### Запуск +1. Токен от BotFather в `.env` +2. `pip install -r requirements.txt` +3. `python -m bot.main` + +## Не сделано (можно следующим шагом) +- Категории трат и отчёты за период +- Напоминание вечером, если лимит не выбран +- Мультивалютность / несколько бюджетов +- Деплой (systemd / Docker) diff --git a/context/2026-09-13_04-51-08_dockerfile.md b/context/2026-09-13_04-51-08_dockerfile.md new file mode 100644 index 0000000..f754e46 --- /dev/null +++ b/context/2026-09-13_04-51-08_dockerfile.md @@ -0,0 +1,15 @@ +# 2026-09-13 — Dockerfile для бота + +## Запрос +Написать Dockerfile. + +## Сделано +- `Dockerfile` — `python:3.12-slim`, non-root user `bot`, `CMD python -m bot.main` +- `.dockerignore` — исключает `.venv`, `.env`, `data/`, кэши +- В README добавлен пример `docker build` / `docker run` с volume для SQLite + +## Запуск +```bash +docker build -t please-pay-me-bot . +docker run --rm -e BOT_TOKEN=... -v ./data:/app/data please-pay-me-bot +``` diff --git a/context/2026-09-13_05-01-34_docker-xray-proxy.md b/context/2026-09-13_05-01-34_docker-xray-proxy.md new file mode 100644 index 0000000..0da8cda --- /dev/null +++ b/context/2026-09-13_05-01-34_docker-xray-proxy.md @@ -0,0 +1,21 @@ +# 2026-09-13 — SOCKS5-прокси для бота в Docker + +## Запрос +Xray на хосте (10808/10809) слушает 127.0.0.1; контейнер его не видит. Нужно открыть listen на 0.0.0.0 и настроить aiogram через SOCKS5. + +## Сделано в репозитории +- `aiohttp-socks` в `requirements.txt` +- `PROXY_URL` в `bot/config.py` (опционально) +- `bot/main.py` — `AiohttpSession(proxy=...)` (официальный API aiogram 3, без `_connector_init`) +- `scripts/open_xray_listen.sh` — правки Xray на Ubuntu-хосте + restart +- `docker-compose.yml` — `PROXY_URL=socks5://172.17.0.1:10808` по умолчанию +- README: секция прокси + firewall warning + +## На хосте (вручную) +```bash +./scripts/open_xray_listen.sh +# в .env: PROXY_URL=socks5://172.17.0.1:10808 +docker compose up -d --build +``` + +Конфиг `/usr/local/etc/xray/config.json` с этой Windows-машины недоступен — правки только через скрипт на Ubuntu. diff --git a/context/2026-09-13_05-06-42_fix-proxy-timeout-host-net.md b/context/2026-09-13_05-06-42_fix-proxy-timeout-host-net.md new file mode 100644 index 0000000..470c52b --- /dev/null +++ b/context/2026-09-13_05-06-42_fix-proxy-timeout-host-net.md @@ -0,0 +1,17 @@ +# 2026-09-13 — Fix: ProxyTimeout к 172.17.0.1 + +## Симптом +Контейнер: `Proxy connection timed out` к `socks5://172.17.0.1:10808`. + +## Причина +В bridge/Portainer-сети хост `172.17.0.1:10808` часто недоступен (Xray на 127.0.0.1 и/или другая docker-сеть). + +## Исправление +- `docker-compose.yml`: `network_mode: host` + `PROXY_URL=socks5://127.0.0.1:10808` +- Убран захардкоженный `BOT_TOKEN` из compose (был в логах/файле — нужен revoke у BotFather) +- Xray можно оставить на `127.0.0.1` (безопаснее, чем 0.0.0.0) + +## Действия на сервере +1. BotFather → revoke/перевыпустить токен +2. В `.env`: новый токен + `PROXY_URL=socks5://127.0.0.1:10808` +3. `docker compose up -d --build` diff --git a/context/2026-09-13_05-11-16_dated-expenses.md b/context/2026-09-13_05-11-16_dated-expenses.md new file mode 100644 index 0000000..ecb8a56 --- /dev/null +++ b/context/2026-09-13_05-11-16_dated-expenses.md @@ -0,0 +1,13 @@ +# 2026-09-13 — Траты за произвольную дату + +## Запрос +Возможность добавлять трату за определённое число. + +## Сделано +- Парсер: `за 12.09 250 кофе`, `250 кофе за 12.09`, `12.09 250`, `250 кофе 12.09` +- Дата без года для трат берёт прошлое/текущее (не будущее), в отличие от даты бюджета +- `BudgetService.add_expense(..., spent_at=)` с проверкой периода бюджета +- FSM `/spend` и кнопка «📅 За дату» +- `/day 12.09` — список трат за дату + +Месяц в дате с точкой лучше писать двузначно (`12.09`), чтобы не путать с суммой `12.5`. diff --git a/context/2026-09-13_05-15-40_fix-dated-fsm-stuck.md b/context/2026-09-13_05-15-40_fix-dated-fsm-stuck.md new file mode 100644 index 0000000..4f8baf3 --- /dev/null +++ b/context/2026-09-13_05-15-40_fix-dated-fsm-stuck.md @@ -0,0 +1,11 @@ +# 2026-09-13 — Fix: FSM «За дату» глотал все сообщения + +## Баг +После «📅 За дату» состояние `waiting_date` было зарегистрировано раньше `/status`, `/today`, `/day`. Любой ввод (включая кнопки) шёл в парсер даты. Сообщение `12.09 кб` целиком не парсилось как дата → вечный цикл ошибки. + +## Исправление +- Команды и кнопки меню регистрируются **до** FSM и сбрасывают state +- На шаге даты: one-shot `12.09 250 кб`, либо дата из первого токена (`12.09 кб` → дата + заметка, потом спросить сумму) +- Подсказка про `/cancel` + +После деплоя: если бот всё ещё «залип» — один раз `/cancel` или `/help`. diff --git a/context/2026-09-13_05-20-03_period-history-pagination.md b/context/2026-09-13_05-20-03_period-history-pagination.md new file mode 100644 index 0000000..9a68df3 --- /dev/null +++ b/context/2026-09-13_05-20-03_period-history-pagination.md @@ -0,0 +1,11 @@ +# 2026-09-13 — Постраничный просмотр трат периода + +## Запрос +Просматривать траты за текущий период бюджета постранично. + +## Сделано +- Репозиторий: `count/sum/list_expenses_in_period` с LIMIT/OFFSET +- `BudgetService.get_period_expenses_page` (по 8 записей, новые сверху) +- `/history` и кнопка «📒 Период» +- Inline «‹ Назад» / «N/M» / «Вперёд ›» +- Сумма статуса периода тоже считается строго в границах бюджета diff --git a/context/2026-09-13_06-55-26_api-web-cabinet.md b/context/2026-09-13_06-55-26_api-web-cabinet.md new file mode 100644 index 0000000..a823532 --- /dev/null +++ b/context/2026-09-13_06-55-26_api-web-cabinet.md @@ -0,0 +1,27 @@ +# 2026-09-13 — API + веб-кабинет для бюджетов и трат + +## Запрос +Сделать API и фронт для просмотра бюджетов и трат. + +## Сделано +### API (`api/`) +- FastAPI, тот же SQLite (`data/budget.db`, WAL) +- `GET /api/health` без токена +- `GET /api/budgets`, `/api/budgets/{user_id}`, `/api/budgets/{user_id}/expenses` +- Auth: `X-API-Token` / Bearer `API_TOKEN` + +### Web (`web/`) +- React + Vite + React Router +- Список бюджетов, деталка со статусом и постраничными тратами +- Токен в localStorage + +### Docker +- `Dockerfile.api` (multi-stage: npm build + uvicorn) +- `docker-compose.yml`: сервисы `bot` + `web` (:8000) + +## Запуск +```bash +# .env: API_TOKEN=... +docker compose up -d --build +# кабинет: http://HOST:8000 +``` diff --git a/context/2026-09-13_07-18-38_clarify-compose-tokens.md b/context/2026-09-13_07-18-38_clarify-compose-tokens.md new file mode 100644 index 0000000..e599665 --- /dev/null +++ b/context/2026-09-13_07-18-38_clarify-compose-tokens.md @@ -0,0 +1,8 @@ +# 2026-09-13 — Уточнение docker-compose: BOT_TOKEN vs API_TOKEN + +## Вопрос +Почему у bot убран токен и зачем фронту токен бота? + +## Ответ +1. **BOT_TOKEN не убирался у бота** — убрали только хардкод из compose (токен светился в логах). Бот читал его из `.env` через `env_file`. Сейчас явно: `BOT_TOKEN: ${BOT_TOKEN}`. +2. **Фронту BOT_TOKEN не нужен.** Нужен отдельный `API_TOKEN` для защиты read-only API кабинета. У сервиса `web` убран `env_file`, чтобы Telegram-токен туда вообще не попадал. diff --git a/context/2026-09-13_07-20-59_split-web-api-ports.md b/context/2026-09-13_07-20-59_split-web-api-ports.md new file mode 100644 index 0000000..85f269b --- /dev/null +++ b/context/2026-09-13_07-20-59_split-web-api-ports.md @@ -0,0 +1,10 @@ +# 2026-09-13 — Разделение портов фронт/API + +## Запрос +Фронт на внешнем порту 51290, API на 51291; фронт ходит на localhost:51291. + +## Сделано +- `api` сервис: `51291:8000`, только FastAPI (без статики) +- `web` сервис: nginx `51290:80`, `VITE_API_BASE_URL=http://localhost:51291` +- CORS по умолчанию: `http://localhost:51290`, `http://127.0.0.1:51290` +- `web/src/api.ts` собирает абсолютные URL к API diff --git a/context/2026-09-13_07-28-00_pretty-404.md b/context/2026-09-13_07-28-00_pretty-404.md new file mode 100644 index 0000000..db3bd82 --- /dev/null +++ b/context/2026-09-13_07-28-00_pretty-404.md @@ -0,0 +1,4 @@ +# 2026-09-13 — Красивая 404 + +Стилизована под кабинет (Syne/Manrope, зелёный градиент, motion). +Файлы: корневой `404.html` + `web/public/404.html`, nginx `error_page 404`. diff --git a/context/2026-09-13_07-47-00_nginx-api-proxy.md b/context/2026-09-13_07-47-00_nginx-api-proxy.md new file mode 100644 index 0000000..5203703 --- /dev/null +++ b/context/2026-09-13_07-47-00_nginx-api-proxy.md @@ -0,0 +1,4 @@ +# 2026-09-13 — Nginx proxy /api → API + +Фронт ходит на same-origin `/api`. Nginx проксирует на `http://api:8000` (сервис compose = хост `localhost:51291`). +`VITE_API_BASE_URL` по умолчанию пустой. Vite dev proxy → `http://localhost:51291`. diff --git a/context/2026-09-13_07-51-00_fix-api-readonly-sqlite.md b/context/2026-09-13_07-51-00_fix-api-readonly-sqlite.md new file mode 100644 index 0000000..aa5a72c --- /dev/null +++ b/context/2026-09-13_07-51-00_fix-api-readonly-sqlite.md @@ -0,0 +1,11 @@ +# 2026-09-13 — Fix: API readonly database + +## Ошибка +`sqlite3.OperationalError: attempt to write a readonly database` на `PRAGMA journal_mode=WAL`. + +## Причина +API (uid 1001) и бот (uid 1000) делили `./data`; WAL/схема требуют запись. + +## Исправление +- `Database(read_only=True)` для API: `file:...?mode=ro`, без WAL и SCHEMA +- `Dockerfile.api` — uid 1000 как у бота diff --git a/context/2026-09-13_07-54-00_fix-nginx-api-html-json.md b/context/2026-09-13_07-54-00_fix-nginx-api-html-json.md new file mode 100644 index 0000000..480a341 --- /dev/null +++ b/context/2026-09-13_07-54-00_fix-nginx-api-html-json.md @@ -0,0 +1,11 @@ +# 2026-09-13 — Fix: JSON.parse на фронте + +## Симптом +`JSON.parse: unexpected character at line 1 column 1` — в ответ на `/api/budgets` приходил HTML `index.html` (SPA fallback). + +## Причина +В `web/nginx.conf` не было `location /api/` → `try_files` отдавал фронт. + +## Исправление +Вернул `proxy_pass http://api:8000` для `/api/`, улучшил разбор ошибок во фронте. +Пересобрать: `docker compose up -d --build web` diff --git a/context/2026-09-13_07-59-21_telegram-login-cabinet.md b/context/2026-09-13_07-59-21_telegram-login-cabinet.md new file mode 100644 index 0000000..0aa7fd9 --- /dev/null +++ b/context/2026-09-13_07-59-21_telegram-login-cabinet.md @@ -0,0 +1,10 @@ +# 2026-09-13 — Личный кабинет через Telegram Login + +## Сделано +- POST `/api/auth/telegram` — проверка Login Widget + JWT +- GET `/api/me`, `/api/me/budget`, `/api/me/expenses` — только свой user_id +- Фронт: кнопка Telegram Login, кабинет своего бюджета +- Env: `BOT_TOKEN`, `TELEGRAM_BOT_USERNAME` (+ опционально `API_TOKEN` для админских `/api/budgets`) + +## BotFather +`/setdomain` на домен кабинета (нужен публичный HTTPS). diff --git a/context/2026-09-13_08-11-27_web-write-expenses-budget.md b/context/2026-09-13_08-11-27_web-write-expenses-budget.md new file mode 100644 index 0000000..a75000d --- /dev/null +++ b/context/2026-09-13_08-11-27_web-write-expenses-budget.md @@ -0,0 +1,13 @@ +# 2026-09-13 — Write API + формы трат/бюджета в кабинете + +## Сделано +- API: `read_only=False` +- `POST /api/me/expenses`, `DELETE /api/me/expenses/last`, `PUT /api/me/budget` +- Фронт: формы «Добавить трату» и «Задать/обновить бюджет» +- CORS: PUT/DELETE + +## На сервере +```bash +sudo chown -R 1000:1000 ./data +docker compose up -d --build api web +``` diff --git a/context/2026-09-13_08-25-18_web-ui-redesign.md b/context/2026-09-13_08-25-18_web-ui-redesign.md new file mode 100644 index 0000000..094c896 --- /dev/null +++ b/context/2026-09-13_08-25-18_web-ui-redesign.md @@ -0,0 +1,9 @@ +# 2026-09-13 — Редизайн web-кабинета + +## UX +- Login: brand-first, один CTA (Telegram) +- Кабинет: hero = остаток; быстрая трата; история; настройки бюджета в `
` +- Flash с aria-live, skeleton, focus-visible, reduced-motion +- Крупные touch-target (48px), sticky topbar + +Сохранить палитру леса/зелени проекта. Пересборка: `docker compose up -d --build web` diff --git a/context/2026-09-13_10-00-00_web-strict-system-ui.md b/context/2026-09-13_10-00-00_web-strict-system-ui.md new file mode 100644 index 0000000..4caf343 --- /dev/null +++ b/context/2026-09-13_10-00-00_web-strict-system-ui.md @@ -0,0 +1,4 @@ +# 2026-09-13 — Строгий системный UI кабинета + +Светлая тема, IBM Plex Sans/Mono, шкала отступов 4–48px, hairline-границы, +без glow/градиентов. Суммы в mono. Секции: Обзор → Операция → Журнал → Параметры. diff --git a/context/2026-09-13_10-15-00_product-design-system.md b/context/2026-09-13_10-15-00_product-design-system.md new file mode 100644 index 0000000..04025aa --- /dev/null +++ b/context/2026-09-13_10-15-00_product-design-system.md @@ -0,0 +1,26 @@ +# 2026-09-13 — Системный продуктовый дизайн (web) + +Заложена расширяемая дизайн-система кабинета вместо монолита `App.tsx`. + +## Структура + +- `web/src/design/` — токены (`tokens.css`), base, стили shell/UI +- `web/src/components/ui/` — Button, Field, Flash, ProgressBar, MetricGrid, PageHeader, Section, EmptyState, Banner +- `web/src/components/layout/` — AppShell, AuthLayout, `PRIMARY_NAV` (точка расширения меню) +- `web/src/auth/` — AuthProvider, RequireAuth +- `web/src/cabinet/` — CabinetProvider (бюджет/траты), CabinetLayout +- `web/src/pages/` — Login, Overview, Operations, Journal, Period (+ ComingSoon заготовка) + +## Маршруты + +`/login` · `/` · `/operations` · `/journal` · `/period` +В nav зарезервированы «Отчёты» и «Настройки» (`soon: true`). + +## Как добавлять фичу + +1. Пункт в `PRIMARY_NAV` +2. Route под `CabinetLayout` в `App.tsx` +3. Страница в `pages/`, UI из `components/ui` +4. Доменное состояние — в `cabinet/` или новый context + +Сборка `npm run build` проходит успешно. diff --git a/context/2026-09-13_10-25-00_bot-via-api.md b/context/2026-09-13_10-25-00_bot-via-api.md new file mode 100644 index 0000000..f46c8b1 --- /dev/null +++ b/context/2026-09-13_10-25-00_bot-via-api.md @@ -0,0 +1,18 @@ +# 2026-09-13 — Бот ходит в БД только через API + +## Цель +Единый путь записи/чтения: web и Telegram-бот → FastAPI → SQLite. +Бот больше не монтирует `./data` и не открывает `budget.db`. + +## API +- `POST /api/auth/internal` + `X-API-Token` / `API_TOKEN` → JWT как у web +- Бот вызывает те же `/api/me/budget`, `/api/me/expenses`, undo, upsert +- `GET /api/me/expenses?spent_at=YYYY-MM-DD` — траты за день (для /today, /day) + +## Bot +- `bot/clients/budget_api.py` — `BudgetApiClient` (aiohttp) +- `bot/config.py`: `API_BASE_URL`, `API_TOKEN` (обязателен) +- Compose: bot без volume data; `API_BASE_URL=http://127.0.0.1:51291` при host network + +## Деплой +Пересобрать `api` + `bot`, убедиться что `API_TOKEN` одинаковый в обоих сервисах. diff --git a/context/2026-09-13_10-40-00_multi-budget-active.md b/context/2026-09-13_10-40-00_multi-budget-active.md new file mode 100644 index 0000000..e4f41e2 --- /dev/null +++ b/context/2026-09-13_10-40-00_multi-budget-active.md @@ -0,0 +1,23 @@ +# 2026-09-13 — Несколько бюджетов + is_active + +## Модель +- У пользователя много строк в `budgets` (снят UNIQUE user_id) +- Поля: `name`, `is_active`, `users.selected_budget_id` +- `expenses.budget_id` — траты привязаны к конкретному бюджету +- Миграция на старте API (`migrate_schema`) для старых БД + +## Семантика +- **Активный** — можно писать траты +- **Текущий (selected)** — default для /status, операций, журнала +- Неактивный отклоняет новые expenses + +## API +- `GET/POST /api/me/budgets` +- `PUT /api/me/budgets/{id}` +- `PATCH /api/me/budgets/{id}/active` +- `POST /api/me/budgets/{id}/select` +- `budget_id` на expenses / budget GET + +## Клиенты +- Web: страница `/budgets`, переключатель вкл/выкл и «текущий» +- Bot: `/budgets`, `/budget` создаёт новый; inline select/toggle diff --git a/context/2026-09-13_10-50-00_csharp-api-postgres.md b/context/2026-09-13_10-50-00_csharp-api-postgres.md new file mode 100644 index 0000000..7a7e067 --- /dev/null +++ b/context/2026-09-13_10-50-00_csharp-api-postgres.md @@ -0,0 +1,18 @@ +# 2026-09-13 — API на C# + PostgreSQL + +## Что сделано +- Новый стек: `src/PleasePayMe.*` (Domain / Application / Infrastructure / Api) +- EF Core + Npgsql, миграция `InitialCreate`, авто-`Migrate()` на старте +- HTTP-контракт прежний (`/api/auth/*`, `/api/me/*`, `/api/budgets/*`, snake_case JSON, `{detail}`) +- Docker: сервис `db` (postgres:16), `Dockerfile.api` → ASP.NET +- Python `api/` помечен deprecated; бот и web без смены контракта + +## Запуск +```bash +docker compose up -d --build +``` +Переменные: `BOT_TOKEN`, `API_TOKEN`, `POSTGRES_*`. + +## Данные +SQLite `data/budget.db` в compose больше не используется. +Перенос старых данных — отдельной задачей (не автоматический). diff --git a/context/2026-09-13_10-55-00_delete-budgets.md b/context/2026-09-13_10-55-00_delete-budgets.md new file mode 100644 index 0000000..dfe9e73 --- /dev/null +++ b/context/2026-09-13_10-55-00_delete-budgets.md @@ -0,0 +1,5 @@ +# 2026-09-13 — Удаление бюджетов + +- `DELETE /api/me/budgets/{id}` → 204; траты каскадом; selected переназначается +- Web: кнопка «Удалить» с confirm на странице Бюджеты +- Bot: кнопка 🗑 в `/budgets` diff --git a/context/2026-09-13_11-00-00_work-jobs-section.md b/context/2026-09-13_11-00-00_work-jobs-section.md new file mode 100644 index 0000000..362384b --- /dev/null +++ b/context/2026-09-13_11-00-00_work-jobs-section.md @@ -0,0 +1,14 @@ +# 2026-09-13 — Раздел «Работа» + +## Модель +- Таблица `jobs`: name, salary_amount, pay_days (integer[] 1–31), is_active +- Если дня нет в месяце (напр. 31) — берётся последний день месяца +- API считает `next_pay_dates` (ближайшие выплаты) + +## API +- `GET/POST /api/me/jobs` +- `PUT/DELETE /api/me/jobs/{id}` + +## Web +- Навигация «Работа» → `/work` +- Список + форма: название, ЗП, дни (текст `5, 20` + быстрые чипы) diff --git a/context/2026-09-13_11-10-00_job-split-weekend.md b/context/2026-09-13_11-10-00_job-split-weekend.md new file mode 100644 index 0000000..1ba5121 --- /dev/null +++ b/context/2026-09-13_11-10-00_job-split-weekend.md @@ -0,0 +1,6 @@ +# 2026-09-13 — Работа: доля выплат + сдвиг с выходных + +- Максимум 2 дня в `pay_days` +- `first_pay_percent` — доля более раннего дня; второй = 100 − first +- `weekend_policy`: `before_weekend` | `after_weekend` (сб/вс → пт или пн) +- `next_pays[]`: дата (уже с учётом сдвига), scheduled_day, percent, amount diff --git a/context/2026-09-13_11-20-00_budget-start-date.md b/context/2026-09-13_11-20-00_budget-start-date.md new file mode 100644 index 0000000..973cc94 --- /dev/null +++ b/context/2026-09-13_11-20-00_budget-start-date.md @@ -0,0 +1,5 @@ +# 2026-09-13 — Выбор даты начала бюджета + +- API уже принимал `start_date` (create/update/upsert); upsert теперь пробрасывает дату. +- Web: поле «С даты» в Бюджеты и Период; по умолчанию `todayIso()` при создании. +- Bot client: `create_budget` / `set_budget` передают `start_date`, если задан (иначе API = сегодня). diff --git a/context/2026-09-13_11-25-00_calendar-budgets-pays.md b/context/2026-09-13_11-25-00_calendar-budgets-pays.md new file mode 100644 index 0000000..cc5117b --- /dev/null +++ b/context/2026-09-13_11-25-00_calendar-budgets-pays.md @@ -0,0 +1,6 @@ +# 2026-09-13 — Календарь бюджетов и выплат + +- Маршрут `/calendar`, пункт «Календарь» в nav +- Полоски цвета = периоды бюджетов (стабильный цвет по id) +- Кружки = дни ЗП (с учётом weekend_policy, как в PaySchedule) +- Клик по дню — детали; легенда ниже сетки diff --git a/context/2026-09-13_11-30-00_calendar-expenses.md b/context/2026-09-13_11-30-00_calendar-expenses.md new file mode 100644 index 0000000..a87e83e --- /dev/null +++ b/context/2026-09-13_11-30-00_calendar-expenses.md @@ -0,0 +1,5 @@ +# 2026-09-13 — Календарь 2.0: траты на днях + +- API `GET /api/me/expenses/range?from=&to=` — траты по всем бюджетам пользователя (≤93 дня) +- Ячейка: точка + компактная сумма трат за день +- Клик по дню → блок «Операции» (сумма, заметка, бюджет) + контекст (бюджеты/ЗП) diff --git a/context/2026-09-19_22-30-00_expenses-without-active-all-journal.md b/context/2026-09-19_22-30-00_expenses-without-active-all-journal.md new file mode 100644 index 0000000..3c31824 --- /dev/null +++ b/context/2026-09-19_22-30-00_expenses-without-active-all-journal.md @@ -0,0 +1,6 @@ +# 2026-09-19 — Траты без активного бюджета + журнал «все» + +- `AddExpense`: больше не требует `is_active`; для неактивного бюджета даты периода не ограничивают +- `GET /api/me/expenses?all=true` — пагинация по всем бюджетам пользователя (`budget_id: null`) +- Журнал: переключатель **Все / Текущий** (по умолчанию все); в режиме «все» видно имя бюджета +- Операции: выбор бюджета в форме, в т.ч. выключенного diff --git a/context/2026-09-19_23-10-00_mobile-widgetbook.md b/context/2026-09-19_23-10-00_mobile-widgetbook.md new file mode 100644 index 0000000..c24fc75 --- /dev/null +++ b/context/2026-09-19_23-10-00_mobile-widgetbook.md @@ -0,0 +1,18 @@ +# 2026-09-19 — Flutter mobile + portable Widgetbook + +## Mobile app (`mobile/`) +- Flutter scaffold (android/ios/web/windows) +- Theme tokens aligned with web `tokens.css` + light/dark `ThemeData` +- UI kit: atoms / molecules / navigation / feedback / sample screens +- IBM Plex Sans via `google_fonts` + +## Widgetbook (`mobile/widgetbook/`) +- Separate package (`please_pay_me_widgetbook`) path-depends on app +- Catalog: Atoms, Molecules, Navigation, Feedback, Screens + knobs +- Addons: MaterialTheme (light/dark), Localization (ru/en), Viewport (iPhone/Android) +- Smoke tests: `flutter test` — all use-cases registered + +## Run +```bash +cd mobile/widgetbook && flutter pub get && flutter run -d chrome +``` diff --git a/context/2026-09-19_23-20-00_mobile-run-ps1.md b/context/2026-09-19_23-20-00_mobile-run-ps1.md new file mode 100644 index 0000000..845ea21 --- /dev/null +++ b/context/2026-09-19_23-20-00_mobile-run-ps1.md @@ -0,0 +1,5 @@ +# 2026-09-19 — PS1 скрипты запуска mobile/widgetbook + +- `mobile/run.ps1` — app или `-Target widgetbook` +- `mobile/widgetbook/run.ps1` — каталог UI (`-Device chrome|windows|edge`) +- Ищут Flutter в PATH и `%USERPROFILE%\flutter\bin` diff --git a/context/2026-09-19_23-25-00_widgetbook-windows-support.md b/context/2026-09-19_23-25-00_widgetbook-windows-support.md new file mode 100644 index 0000000..d778e5d --- /dev/null +++ b/context/2026-09-19_23-25-00_widgetbook-windows-support.md @@ -0,0 +1,5 @@ +# 2026-09-19 — Widgetbook: добавлен Windows desktop support + +- `flutter create . --platforms=windows,web,android,ios` в `mobile/widgetbook` +- Исправлена заглушка `test/widget_test.dart` после create +- Повтор: `.\run.ps1 -Device windows` diff --git a/context/2026-09-19_23-55-00_mobile-ios-kit.md b/context/2026-09-19_23-55-00_mobile-ios-kit.md new file mode 100644 index 0000000..e15f1d3 --- /dev/null +++ b/context/2026-09-19_23-55-00_mobile-ios-kit.md @@ -0,0 +1,66 @@ +# 2026-09-19 — Мобильный UI-кит переведён на iOS (Cupertino) + +## Запрос +Widgetbook запустился, но Material-компоненты визуально не устроили. Задача — сделать +компоненты «из iOS kit». + +## Что сделано + +### Токены и тема (`mobile/lib/theme/`) +- `tokens.dart` переписан под Apple HIG: семантические цвета (`label`, `secondaryLabel`, + `separator`, `opaqueSeparator`, `groupedBackground`, `groupedSurface`, `barBackground`) + и системная палитра (`systemRed/Orange/Green/Gray/Gray3/Gray5/Gray6`) объявлены как + `CupertinoDynamicColor.withBrightness` — light/dark резолвится самим фреймворком через + `AppColors.of(context, color)`. +- Бренд-тинт остался зелёным (`accent` = #12885A / #3CD68C в тёмной) и подменяет systemBlue. +- Типографика — шкала SF Pro: largeTitle 34 … caption2 11 с нативными letterSpacing. +- Размеры контролов: 50 / 44 / 34 pt, hairline 0.5, радиусы 6/10/12/16/capsule. +- `app_theme.dart` теперь возвращает `CupertinoThemeData` (`buildLightTheme` / + `buildDarkTheme`), шрифт — Inter через google_fonts как кросс-платформенная замена SF Pro + (на Windows/web SF недоступен). + +### Компоненты (`mobile/lib/ui/`) +Material-версии удалены, вместо них Cupertino: +- atoms: `AppButton` (filled/tinted/gray/plain/destructive × large/medium/small), + `AppTextField` (CupertinoTextField + error/prefix/clear), `AppText` (именованные + конструкторы по шкале iOS) + `AppSectionHeader`, `AppIcon` + `AppIconBadge` + (Settings-style скруглённый квадрат). +- molecules: `AppListSection` (inset-grouped, header/footer, hairline с indent), + `AppListTile` (leading/value/chevron, press-highlight, destructive), `AppSwitchRow` + (CupertinoSwitch), `AppCard`, `AppChip` (капсула), `AppAvatar`. +- navigation: `AppNavBar` (реализует `ObstructingPreferredSizeWidget`, есть subtitle), + `AppLargeNavBar` (`CupertinoSliverNavigationBar`), `AppTabBar` (наследник + `CupertinoTabBar`, чтобы отдавать в `CupertinoTabScaffold`), `AppSegmentedControl` + (`CupertinoSlidingSegmentedControl`), `AppAlert` + `showAppAlert` + `showAppActionSheet`. +- feedback: `AppSpinner` (`CupertinoActivityIndicator`), `AppProgressBar` (4pt капсула), + `AppSkeleton` / `AppSkeletonRow`, `AppToast` + `showAppToast` (blur-капсула через + OverlayEntry — в iOS нет SnackBar). +- screens: Feed (large title + сегменты + группы + empty/loading), Detail (hero-сумма, + прогресс, группа параметров, кнопки), Profile (Settings-подобный экран со свитчами). +- `main.dart` переведён на `CupertinoApp` + `CupertinoTabScaffold`. + +### Widgetbook (`mobile/widgetbook/`) +- `Widgetbook.cupertino` + `CupertinoThemeAddon` вместо material-варианта. +- Новый `lib/support/preview.dart` — `IosPreview` кладёт use-case на grouped-фон, + чтобы карточки и разделители читались как в приложении. +- Каталог: Atoms (Button/TextField/Typography/Icons), Molecules (Card/ListTile/SwitchRow/ + Chip/Avatar), Navigation (NavBar/TabBar/SegmentedControl/Alert), Feedback (Progress/ + Skeleton/Toast), Screens (Feed/Detail/Profile) — 42 use-case. +- Knobs: добавлены `knobEnum` и `knobTint` (дропдаун по системным тинтам), убран + deprecated `knobs.list`. + +### Тесты +`widgetbook/test/catalog_smoke_test.dart` больше не проверяет только структуру: каждый +use-case пампится в light и dark внутри `WidgetbookScope` (knobs читают `WidgetbookState` +из контекста, иначе падает «No Widgetbook found in the context»). В тестах отключён +`GoogleFonts.config.allowRuntimeFetching` и вызван `TestWidgetsFlutterBinding.ensureInitialized()`. + +## Результат +- `flutter analyze` (mobile + widgetbook) — 0 issues. +- `flutter test` — 84 теста в widgetbook + 1 в приложении, всё зелёное. + +## Запуск +```powershell +cd mobile\widgetbook +.\run.ps1 -Device windows +``` diff --git a/context/2026-09-20_00-40-00_mobile-app-assembled.md b/context/2026-09-20_00-40-00_mobile-app-assembled.md new file mode 100644 index 0000000..38eca9b --- /dev/null +++ b/context/2026-09-20_00-40-00_mobile-app-assembled.md @@ -0,0 +1,72 @@ +# 2026-09-20 — Мобильное приложение Please Pay Me собрано на iOS-ките + +## Запрос +«Собери теперь приложение please-pay-me, используя эти компоненты» — то есть поверх +Cupertino-кита из прошлой сессии сделать рабочее приложение против существующего API. + +## Что сделано + +### Слой данных (`lib/data/`) +- `models/` — `Budget`, `BudgetStatus` (+ вычисляемые `spentProgress` / `dailyProgress`), + `Expense`, `ExpensesPage`, `Job`, `UpcomingPay`, `WeekendPolicy`, `AuthUser`, `AuthSession`. + Зеркалят `web/src/types.ts`, парсятся через `models/json.dart` — толерантные + коэрсеры (`int`/`double`/`string`), потому что API отдаёт числа из C#-рекордов. +- `api/api_client.dart` — транспорт на `http`: Bearer-токен через `tokenProvider`, + маппинг `detail`/`title` в `ApiException`, отдельная обработка 401 с колбэком + `onUnauthorized` (разлогинивает сессию). +- `repositories/` — интерфейсы `BudgetRepository` / `ExpenseRepository` / + `JobRepository` / `UserRepository` + REST-реализации на все ручки `MeController`. +- `demo/demo_backend.dart` — in-memory бэкенд, повторяющий математику конверта + (`dailyLimit = remaining / daysLeft`). Используется в демо-режиме, превью и тестах; + `DemoBackend.empty()` — для пустых состояний. + +### Ядро (`lib/core/`) +- `config/app_config.dart` — `PPM_API_BASE_URL`, `PPM_WEB_URL`, `PPM_DEMO` через + `--dart-define`; пустой адрес API автоматически включает демо-режим. +- `storage/session_storage.dart` — интерфейс + `SharedPreferences` и in-memory реализации. +- `state/async_value.dart` — sealed `AsyncLoading/AsyncData/AsyncError` с `map(...)`, + чтобы экраны матчились по состоянию, а не жонглировали тремя nullable-полями. +- `format/formatters.dart` — деньги (`1 234,50 ₽`), русские плюрали, «Сегодня/Вчера». + +### Состояние (`lib/features/*/`) +- `SessionController` — владеет авторизацией и выдаёт репозитории (REST или demo), + так что остальное приложение не знает про токены. `sessionKey` пересобирает + фичевые контроллеры при входе/выходе. +- `BudgetsController` — источник правды по конвертам (список, выбранный, мутации + с авто-перезагрузкой, возврат текста ошибки вместо исключения). +- `JournalController` — пагинация + группировка по дням + скоуп «текущий/все». +- `JobsController` — работы и ближайшая выплата. + +### Экраны (`lib/features/`, `lib/app/`) +Пять вкладок: Обзор, Журнал, Бюджеты, Работа, Профиль (+ экран входа и сплэш). +Формы — модальные шиты: новая/редактирование траты, бюджета, работы; все они +принимают `onSubmit`, возвращающий текст ошибки, и не знают про репозитории. +`_SessionGate` разводит `restoring / signedOut / signedIn`. + +### Кит +Удалены демо-экраны `lib/ui/screens/*`; добавлены `AppEmptyState`, `AppErrorView`, +`AppLoadingView`, `showAppDatePicker`, `showAppFormSheet`. + +### Widgetbook +Папка Screens теперь показывает **настоящие экраны** приложения на `DemoScope` +(demo-бэкенд + провайдеры), включая пустые состояния, формы и полный таб-бар. + +### Авторизация — известное ограничение +Telegram Login Widget работает только в вебе. Мобильный клиент принимает JWT из +`localStorage.ppm_session_jwt` веб-кабинета и валидирует его через `GET /api/me`. +Нативный вход (WebView с Telegram-виджетом либо one-time code через бота) +не делался — это отдельное продуктовое решение. + +## Тесты +- `mobile`: 32 теста — парсинг моделей, форматтеры, все контроллеры на demo-бэкенде, + виджет-сценарии (вход, табы, пустые состояния, запись траты end-to-end). +- `mobile/widgetbook`: 100 — каждый use-case рендерится в light и dark. +- `flutter analyze` по обоим пакетам — 0 issues. +- `flutter build windows --debug` проходит (проверка плагинов shared_preferences/url_launcher). + +## Запуск +```powershell +cd mobile +.\run.ps1 # демо-данные +.\run.ps1 -ApiBaseUrl https://ppm.example.com +``` diff --git a/context/2026-09-20_01-20-00_telegram-webview-login.md b/context/2026-09-20_01-20-00_telegram-webview-login.md new file mode 100644 index 0000000..4eb75d3 --- /dev/null +++ b/context/2026-09-20_01-20-00_telegram-webview-login.md @@ -0,0 +1,69 @@ +# 2026-09-20 — Вход через WebView + Telegram Login Widget + +## Запрос +Реализовать нативный вход: открыть кабинет в WebView и забрать JWT через +JS-канал. Бэкенд не менять. + +## Почему не «просто открыть страницу» + +Telegram Login Widget в обычном браузере работает через iframe + `data-onauth`. +В Android/iOS WebView этот путь ломается: + +- popup (`window.open` на `oauth.telegram.org`) не возвращает `opener`; +- iframe режется third-party cookies и User-Agent с маркером `; wv`; +- callback `onTelegramAuth` просто не вызывается. + +Поэтому в WebView виджет переключается на **redirect-режим** (`data-auth-url`), +а Flutter забирает уже готовый JWT, который кабинет и так кладёт в +`localStorage.ppm_session_jwt`. API (`POST /api/auth/telegram`) не трогали. + +## Поток + +1. Экран входа на iOS/Android показывает «Войти через Telegram». +2. Открывается fullscreen WebView на `{cabinet}/login`. +3. Кабинет видит канал `window.PpmAuth` → ставит виджету `data-auth-url=/login`. +4. Пользователь логинится в Telegram; редирект приходит на `/login?id&hash&…`. +5. `telegramPayloadFromQuery` собирает payload, кабинет зовёт тот же + `loginWithTelegram` / `POST /api/auth/telegram`. +6. `setSession` пишет JWT в `localStorage` и зовёт `PpmAuth.postMessage`. +7. Flutter парсит сообщение, закрывает WebView, проверяет токен через + `GET /api/me`, кладёт сессию в `SharedPreferences`. + +Запасные пути, если кабинет ещё не задеплоен с мостом: + +- JS в WebView хукает `localStorage.setItem('ppm_session_jwt')` и поллит ключ; +- на Windows/web WebView нет — форма «вставить токен»; +- «Демо-режим» по-прежнему без сети. + +## Что изменилось + +### Web (не API) +- `web/src/auth/telegramRedirect.ts` — разбор query и `postTokenToNativeApp`. +- `TelegramLoginButton` — `data-auth-url` только если есть `window.PpmAuth`. +- `LoginPage` — автологин, если в URL уже лежит telegram-payload. +- `setSession` — после записи токена шлёт его в канал. + +### Mobile +- `TelegramLoginScreen` (`webview_flutter`): канал `PpmAuth`, Chrome UA без + `; wv`, third-party cookies на Android, `tg://` уходит во внешнее приложение, + кнопка «назад» после oauth.telegram.org. +- `resolveCabinetLoginUri` всегда открывает `/login`. +- `INTERNET` в main `AndroidManifest` (раньше был только в debug). +- iOS: `LSApplicationQueriesSchemes` для `tg` / `telegram`. +- `PPM_WEB_URL` по умолчанию = `PPM_API_BASE_URL` (один origin за nginx). + +## Тесты +- `resolveApiBaseUrl` / `resolveCabinetLoginUri` / парсер канала. +- виджет: инжектированный launcher отдаёт JWT → `GET /api/me` → signed in. +- 32 теста приложения, 102 в Widgetbook, `flutter analyze` чистый. + +## Запуск против живого кабинета + +```powershell +cd mobile +.\run.ps1 -Device windows # на Windows будет форма токена +# на телефоне: +.\run.ps1 -ApiBaseUrl https://<домен-кабинета> +``` + +Нужен публичный HTTPS и BotFather `/setdomain` на этот домен — как для веба. diff --git a/context/2026-09-20_01-35-00_mobile-dotenv.md b/context/2026-09-20_01-35-00_mobile-dotenv.md new file mode 100644 index 0000000..5115bad --- /dev/null +++ b/context/2026-09-20_01-35-00_mobile-dotenv.md @@ -0,0 +1,22 @@ +# 2026-09-20 — API URL мобильного приложения из `.env` + +## Запрос +Задавать `ApiBaseUrl` через переменные `.env`, а не флагом `run.ps1`. + +## Решение +Конфиг читается из `mobile/.env` и уходит во Flutter как +`--dart-define-from-file` — без `flutter_dotenv` в ассетах (иначе gitignored +`.env` ломал бы CI: asset обязан существовать в момент сборки). + +Порядок в `AppConfig.fromEnvironment`: +1. `--dart-define` (в т.ч. из файла) +2. явно переданная map (тесты) +3. пустой URL → демо-режим + +`run.ps1`: если `.env` нет — копирует `.env.example`. `-ApiBaseUrl` / `-WebUrl` +остались как разовый override поверх файла. Скрипт сохранён в UTF-8 с BOM и +без вложенных кавычек — иначе PowerShell 5.1 на Windows-1251 сыпется +`TerminatorExpectedAtEndOfString`. + +Ключи: `PPM_API_BASE_URL`, `PPM_WEB_URL`, `PPM_DEMO`. +Локальный `.env` в gitignore (есть и в корневом). diff --git a/context/2026-09-20_02-00-00_yandex-login.md b/context/2026-09-20_02-00-00_yandex-login.md new file mode 100644 index 0000000..632c79c --- /dev/null +++ b/context/2026-09-20_02-00-00_yandex-login.md @@ -0,0 +1,37 @@ +# 2026-09-20 — Вход через Яндекс ID (web + mobile) + +## Запрос +Кнопка «Войти через Яндекс» в кабинете и в приложении. Client ID / secret +уже заведены в Yandex OAuth. + +## Решение +Authorization code. Secret только на API (`YANDEX_CLIENT_SECRET` в `.env`, +не в web/mobile и не в git). Клиенты получают `client_id` с +`GET /api/auth/providers` и шлют `code` + `redirect_uri` в +`POST /api/auth/yandex`. + +API меняет код на токен Яндекса (`oauth.yandex.ru/token`), читает профиль +(`login.yandex.ru/info`) и выдаёт тот же JWT, что и Telegram. + +`users.user_id` для Яндекса: ` (1 << 50) | yandexId ` — не пересекается с +Telegram id и остаётся внутри `Number.MAX_SAFE_INTEGER`, чтобы JSON в +браузере и Flutter не терял точность. + +`redirect_uri` сверяется с allowlist (продакшен `https://please-pay-me.ru/`, +localhost:51290 и :5173, плюс `YANDEX_REDIRECT_URI` / `YANDEX_REDIRECT_URIS`). + +## Потоки + +- Web: кнопка → `oauth.yandex.ru/authorize` → возврат на `/?code=` + (как в кабинете Яндекса) → `RequireAuth` переносит query на `/login` → + `loginWithYandex` → `setSession`. +- Mobile: своя кнопка → WebView на Яндекс → перехват `{кабинет}/?code=` + → `POST /api/auth/yandex` из Flutter. + +## Что нужно в консоли Яндекса +Redirect URI: `https://please-pay-me.ru/` — как зарегистрировано в Яндексе. + +## Секрет +Значения записаны в корневой `.env` (gitignore). В репозиторий не коммитятся. +Секрет светился в чате — если репозиторий общий, лучше перевыпустить в +кабинете Яндекса. diff --git a/context/2026-09-20_02-10-00_pretty-502.md b/context/2026-09-20_02-10-00_pretty-502.md new file mode 100644 index 0000000..15a671b --- /dev/null +++ b/context/2026-09-20_02-10-00_pretty-502.md @@ -0,0 +1,8 @@ +# 2026-09-20 — Страница 502 + +По аналогии с `404.html`: тот же тёмный градиент, Syne/Manrope, motion. +Текст про недоступный шлюз, кнопки «Обновить» и «На главную». + +Файлы: корневой `502.html` + `web/public/502.html`. +nginx: `error_page 502 503 504 /502.html` — 503/504 на ту же страницу, +потому что при мёртвом API приходит не только 502. diff --git a/context/2026-09-20_02-15-00_yandex-redirect-root.md b/context/2026-09-20_02-15-00_yandex-redirect-root.md new file mode 100644 index 0000000..fe08a04 --- /dev/null +++ b/context/2026-09-20_02-15-00_yandex-redirect-root.md @@ -0,0 +1,8 @@ +# 2026-09-20 — Yandex redirect_uri = корень сайта + +В кабинете Яндекса Callback URL: `https://please-pay-me.ru/` (не `/login`). +Клиенты слали `/login` → Яндекс отвечал «redirect_uri не совпадает». + +Теперь authorize и обмен кода используют `https://please-pay-me.ru/`. +Возврат `/?code=` RequireAuth переносит на `/login?code=`, чтобы SPA +не выкинула query. Mobile перехватывает тот же корень. diff --git a/context/2026-09-20_02-35-00_web-ios-kit.md b/context/2026-09-20_02-35-00_web-ios-kit.md new file mode 100644 index 0000000..d315795 --- /dev/null +++ b/context/2026-09-20_02-35-00_web-ios-kit.md @@ -0,0 +1,22 @@ +# 2026-09-20 — Web UI-кит как в mobile iOS kit + +Кабинет переведён на те же токены и примитивы, что Flutter `mobile/lib/ui`. + +## Токены +`web/src/design/tokens.css` — Apple HIG: accent #12885A / #3CD68C, label / +separator / grouped surfaces, SF-шкала, 4pt сетка, радиусы 6/10/12/16. +Светлая и тёмная тема через `prefers-color-scheme`. Шрифт Inter. +Старые `--color-*` оставлены как алиасы. + +## Кит +`web/src/ui/` — AppButton (filled/tinted/gray/plain/destructive), AppText, +AppTextField, AppListSection/Tile, AppSegmented, AppProgress, AppEmpty/Error, +Chip, Avatar, Skeleton. Стили в `kit.css`. + +Старые `components/ui` (Button, Field, PageHeader, MetricGrid, …) — тонкие +обёртки над китом, страницы не ломаются. + +## Оболочка +AppShell: translucent bar, inset-grouped нав на десктопе, капсулы на узком +экране. Обзор и журнал собраны на list/segmented. Остальные экраны получают +iOS-вид через те же кнопки, поля и `data-list`. diff --git a/context/2026-09-20_02-40-00_login-url-from-env.md b/context/2026-09-20_02-40-00_login-url-from-env.md new file mode 100644 index 0000000..328a3d4 --- /dev/null +++ b/context/2026-09-20_02-40-00_login-url-from-env.md @@ -0,0 +1,5 @@ +# 2026-09-20 — Адрес кабинета только из `.env` + +С экрана входа убрано поле «Адрес кабинета». Origin берётся из +`PPM_WEB_URL` / `PPM_API_BASE_URL` (`https://please-pay-me.ru/`). +Поле токена осталось только в запасном режиме. diff --git a/context/2026-09-20_02-45-00_yandex-unavailable-toast.md b/context/2026-09-20_02-45-00_yandex-unavailable-toast.md new file mode 100644 index 0000000..2e82221 --- /dev/null +++ b/context/2026-09-20_02-45-00_yandex-unavailable-toast.md @@ -0,0 +1,11 @@ +# 2026-09-20 — Почему «Яндекс недоступен» + +Тост шёл из `_yandex == null`: `GET /api/auth/providers` не вызывался, +потому что `AppConfig.fromEnvironment()` читает только `--dart-define`. +Запуск из IDE без `run.ps1` оставлял URL пустым, ошибку глотали. + +На проде `/api/auth/providers` уже отдаёт `enabled: true`. + +Фикс: `main()` читает `mobile/.env` с диска (dart-define по-прежнему +главнее). По тапу провайдеры перезапрашиваются, в тосте — реальная +причина. На Windows WebView нет — отдельное сообщение. diff --git a/context/2026-09-20_02-48-00_config-production-default.md b/context/2026-09-20_02-48-00_config-production-default.md new file mode 100644 index 0000000..aaeefec --- /dev/null +++ b/context/2026-09-20_02-48-00_config-production-default.md @@ -0,0 +1,4 @@ +# 2026-09-20 — Дефолт кабинета + +Если `PPM_API_BASE_URL` / `PPM_WEB_URL` не заданы, `AppConfig` берёт +`https://please-pay-me.ru`. Демо только при `PPM_DEMO=true`. diff --git a/context/2026-09-20_02-50-00_profile-theme-switcher.md b/context/2026-09-20_02-50-00_profile-theme-switcher.md new file mode 100644 index 0000000..8d48d37 --- /dev/null +++ b/context/2026-09-20_02-50-00_profile-theme-switcher.md @@ -0,0 +1,6 @@ +# 2026-09-20 — Переключатель темы в профиле + +На экране профиля: Системная / Светлая / Тёмная (`AppSegmentedControl`). +`ThemeController` хранит выбор в SharedPreferences (`ppm_theme_preference`), +отдельно от JWT — тема переживает logout. `PleasePayMeApp` резолвит +brightness и прокидывает его в `CupertinoTheme` + `MediaQuery.platformBrightness`. diff --git a/context/2026-09-20_02-52-00_android-dark-nav-bar.md b/context/2026-09-20_02-52-00_android-dark-nav-bar.md new file mode 100644 index 0000000..7439fbf --- /dev/null +++ b/context/2026-09-20_02-52-00_android-dark-nav-bar.md @@ -0,0 +1,5 @@ +# 2026-09-20 — Android nav bar в тёмной теме + +Системная нижняя панель: прозрачный чёрный (`#00000000`), без contrast +scrim (`enforceNavigationBarContrast=false`). Иконки светлые. Edge-to-edge +включается в `main`, стиль окна — в `values-night`. diff --git a/context/2026-09-20_02-55-00_splash-screen.md b/context/2026-09-20_02-55-00_splash-screen.md new file mode 100644 index 0000000..edfa3e3 --- /dev/null +++ b/context/2026-09-20_02-55-00_splash-screen.md @@ -0,0 +1,6 @@ +# 2026-09-20 — Сплэш Please Pay Me + +Фирменный экран запуска: зелёный знак ₽, название, слоган, спиннер. +Нативный Android (вкл. 12+ splash API) и iOS launch — тот же фон +`#F2F2F7` / `#000000`, чтобы не было вспышки. `main` рисует сплэш сразу +и держит его минимум 850 мс, пока восстанавливается сессия. diff --git a/context/2026-09-20_03-05-00_legal-docs.md b/context/2026-09-20_03-05-00_legal-docs.md new file mode 100644 index 0000000..cc8cfbd --- /dev/null +++ b/context/2026-09-20_03-05-00_legal-docs.md @@ -0,0 +1,7 @@ +# 2026-09-20 — Правовые документы + +Публичные страницы `/legal/offer|privacy|consent|cookies`. +Футер на логине и в кабинете. Cookie-баннер при первом заходе +(технические / все, отказ от Метрики). Чекбоксы оферты и согласия +на входе в web и mobile; в профиле — «Правовая информация» со +ссылками на веб-версии. Реквизиты ИП Архангельский В.А. diff --git a/context/2026-09-20_03-18-00_app-name-icon.md b/context/2026-09-20_03-18-00_app-name-icon.md new file mode 100644 index 0000000..f332bf0 --- /dev/null +++ b/context/2026-09-20_03-18-00_app-name-icon.md @@ -0,0 +1,5 @@ +# 2026-09-20 — Название и иконка + +Отображаемое имя: **Дожить до ЗП**. Иконка: зелёный квадрат #12885A и белый ₽. +Проставлена в Android (adaptive), iOS, Windows, favicon кабинета. +`AppBrand.name` / `APP_NAME` — единый источник в UI. diff --git a/context/2026-09-20_03-31-00_rustore-icon.md b/context/2026-09-20_03-31-00_rustore-icon.md new file mode 100644 index 0000000..8e66481 --- /dev/null +++ b/context/2026-09-20_03-31-00_rustore-icon.md @@ -0,0 +1,4 @@ +# 2026-09-20 — Иконка для RuStore + +`mobile/assets/branding/rustore_icon_512.png`: 512×512, 1:1, ~145 КБ. +Уменьшена из `app_icon.png` (1024). diff --git a/context/2026-09-20_03-52-00_web-apk-download.md b/context/2026-09-20_03-52-00_web-apk-download.md new file mode 100644 index 0000000..0bf10b9 --- /dev/null +++ b/context/2026-09-20_03-52-00_web-apk-download.md @@ -0,0 +1,6 @@ +# 2026-09-20 — Скачать APK с сайта + +Ссылка `/dozhit-do-zp.apk` на логине и в футере. +Файл: `web/public/dozhit-do-zp.apk` (gitignore, 53 МБ). +Перед деплоем веб скопировать свежий +`mobile/build/app/outputs/flutter-apk/app-release.apk`. diff --git a/context/2026-09-20_04-55-00_telegram-yandex-link.md b/context/2026-09-20_04-55-00_telegram-yandex-link.md new file mode 100644 index 0000000..22030d6 --- /dev/null +++ b/context/2026-09-20_04-55-00_telegram-yandex-link.md @@ -0,0 +1,7 @@ +# 2026-09-20 Telegram → Yandex identity link + +Вход везде только через Яндекс. Бот больше не выдаёт JWT по сырому Telegram `user_id`: пока аккаунт не связан с Яндексом, `POST /api/auth/internal` отвечает 403 `yandex_required` и ссылкой `https://please-pay-me.ru/?tg_link={token}`. + +После входа через Яндекс веб вызывает `POST /api/auth/telegram-link/complete`. API пишет `telegram_yandex_links` и переносит бюджеты / траты / работы с Telegram-id на Yandex-namespaced id (`1<<50 | yandexId`). Дальше бот, кабинет и приложение работают с одним `user_id`. + +`POST /api/auth/telegram` отключён (403). Старые Telegram-сессии в кабинете сбрасываются. diff --git a/data/budget.db-shm b/data/budget.db-shm new file mode 100644 index 0000000..f1e8d76 Binary files /dev/null and b/data/budget.db-shm differ diff --git a/data/budget.db-wal b/data/budget.db-wal new file mode 100644 index 0000000..2d2703b Binary files /dev/null and b/data/budget.db-wal differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5669c74 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,72 @@ +services: + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-please_pay_me} + POSTGRES_USER: ${POSTGRES_USER:-ppm} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-ppm} + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-ppm} -d ${POSTGRES_DB:-please_pay_me}"] + interval: 5s + timeout: 5s + retries: 10 + + bot: + build: . + restart: unless-stopped + network_mode: host + env_file: + - .env + environment: + BOT_TOKEN: ${BOT_TOKEN} + PROXY_URL: ${PROXY_URL:-socks5://127.0.0.1:10808} + API_BASE_URL: ${API_BASE_URL:-http://127.0.0.1:51291} + API_TOKEN: ${API_TOKEN} + depends_on: + api: + condition: service_started + + api: + build: + context: . + dockerfile: Dockerfile.api + restart: unless-stopped + ports: + - "51291:8000" + environment: + BOT_TOKEN: ${BOT_TOKEN} + API_TOKEN: ${API_TOKEN} + JWT_SECRET: ${JWT_SECRET:-} + CORS_ORIGINS: ${CORS_ORIGINS:-*} + YANDEX_CLIENT_ID: ${YANDEX_CLIENT_ID:-} + YANDEX_CLIENT_SECRET: ${YANDEX_CLIENT_SECRET:-} + YANDEX_REDIRECT_URI: ${YANDEX_REDIRECT_URI:-https://please-pay-me.ru/} + YANDEX_REDIRECT_URIS: ${YANDEX_REDIRECT_URIS:-} + PUBLIC_WEB_ORIGIN: ${PUBLIC_WEB_ORIGIN:-https://please-pay-me.ru} + ConnectionStrings__Default: >- + Host=db;Port=5432;Database=${POSTGRES_DB:-please_pay_me};Username=${POSTGRES_USER:-ppm};Password=${POSTGRES_PASSWORD:-ppm} + ASPNETCORE_URLS: http://+:8000 + depends_on: + db: + condition: service_healthy + + web: + build: + context: . + dockerfile: Dockerfile.web + args: + VITE_API_BASE_URL: ${VITE_API_BASE_URL:-} + VITE_TELEGRAM_BOT_USERNAME: ${TELEGRAM_BOT_USERNAME} + restart: unless-stopped + ports: + - "51290:80" + depends_on: + - api + +volumes: + pgdata: diff --git a/mobile/.env.example b/mobile/.env.example new file mode 100644 index 0000000..dca668b --- /dev/null +++ b/mobile/.env.example @@ -0,0 +1,6 @@ +# Optional. Empty keys fall back to https://please-pay-me.ru +# run.ps1 passes this file via --dart-define-from-file. + +PPM_API_BASE_URL=https://please-pay-me.ru/ +PPM_WEB_URL=https://please-pay-me.ru/ +PPM_DEMO=false diff --git a/mobile/.gitignore b/mobile/.gitignore new file mode 100644 index 0000000..4f08a54 --- /dev/null +++ b/mobile/.gitignore @@ -0,0 +1,52 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Widget Preview related +.widget_preview/ + +# Local runtime config (see .env.example) +.env + diff --git a/mobile/.metadata b/mobile/.metadata new file mode 100644 index 0000000..a7644bb --- /dev/null +++ b/mobile/.metadata @@ -0,0 +1,39 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "6a19cca56475dbfba1478ee68d7bd0c2ef891da1" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + base_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + - platform: android + create_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + base_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + - platform: ios + create_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + base_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + - platform: web + create_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + base_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + - platform: windows + create_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + base_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/mobile/README.md b/mobile/README.md new file mode 100644 index 0000000..dbb9221 --- /dev/null +++ b/mobile/README.md @@ -0,0 +1,93 @@ +# Please Pay Me — Flutter mobile + +Мобильный кабинет для бюджета «от зарплаты до зарплаты». UI построен на +iOS-ките (Cupertino, Apple HIG), данные — REST API из `src/PleasePayMe.Api`. + +## Запуск + +```powershell +cd mobile +copy .env.example .env # один раз +# отредактируй PPM_API_BASE_URL в .env +.\run.ps1 +.\run.ps1 -Device chrome +.\run.ps1 -Target widgetbook +``` + +`run.ps1` читает `mobile/.env` и передаёт его во Flutter как +`--dart-define-from-file`. Если файла нет — копирует `.env.example`. + +```bash +export PATH="$HOME/flutter/bin:$PATH" +cd mobile +flutter pub get +flutter run -d windows --dart-define-from-file=.env +``` + +### Конфигурация (`mobile/.env`) + +| Переменная | Назначение | +| --- | --- | +| `PPM_API_BASE_URL` | адрес API; если пусто — `https://please-pay-me.ru` | +| `PPM_WEB_URL` | веб-кабинет; если пусто — тот же origin, что API | +| `PPM_DEMO` | `true` — принудительный in-memory backend | + +### Авторизация + +На iOS и Android: + +- **Telegram** — кабинет в WebView, JWT забирается из + `localStorage.ppm_session_jwt` через канал `PpmAuth`. +- **Яндекс** — WebView на `oauth.yandex.ru`. Код возвращается на + `{кабинет}/` (как Callback URL в кабинете Яндекса), приложение шлёт + его в `POST /api/auth/yandex`. Client secret живёт только на API. + +Приложение проверяет JWT через `GET /api/me` и кладёт сессию в +`SharedPreferences`. + +На Windows / в браузере WebView нет: остаётся ручной ввод токена. Альтернатива +на любой платформе — «Демо-режим» (`DemoBackend` без сети). + +В кабинете Яндекса должен быть Redirect URI `https://<домен>/` +(для продакшена — `https://please-pay-me.ru/`). BotFather → `/setdomain` +для Telegram — тот же домен. + +## Экраны + +| Вкладка | Что делает | +| --- | --- | +| Обзор | остаток бюджета, дневной лимит, трата в один тап, ближайшая выплата | +| Журнал | операции по дням, фильтр «текущий / все бюджеты», подгрузка страниц | +| Бюджеты | выбор активного конверта, создание, редактирование, архив, удаление | +| Работа | оклад, дни выплат, правило выходных, график ближайших зарплат | +| Профиль | пользователь, режим подключения, выход | + +## Структура + +``` +mobile/ +├── lib/ +│ ├── app/ # CupertinoApp, session gate, таб-бар +│ ├── core/ # config, storage, AsyncValue, форматтеры +│ ├── data/ +│ │ ├── api/ # ApiClient (http + маппинг ошибок) +│ │ ├── models/ # Budget/Expense/Job/AuthUser + JSON-хелперы +│ │ ├── repositories/ # интерфейсы + REST-реализации +│ │ └── demo/ # in-memory backend (превью, тесты, демо-режим) +│ ├── features/ # overview / journal / budgets / work / profile / auth +│ ├── theme/ # токены Apple HIG + CupertinoThemeData +│ └── ui/ # дизайн-система (atoms → molecules → navigation) +└── widgetbook/ # переносимый каталог компонентов и экранов +``` + +Слои связаны через интерфейсы репозиториев: экраны знают только +`BudgetRepository` / `ExpenseRepository` / `JobRepository`, а `SessionController` +подставляет REST- или demo-реализацию. Поэтому и Widgetbook, и виджет-тесты +гоняют настоящие экраны без сети. + +## Тесты + +```bash +cd mobile && flutter test # модели, форматтеры, контроллеры, сквозной сценарий +cd mobile/widgetbook && flutter test # рендер каждого use-case в light и dark +``` diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml new file mode 100644 index 0000000..d23a834 --- /dev/null +++ b/mobile/analysis_options.yaml @@ -0,0 +1,36 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/mobile/android/.gitignore b/mobile/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/mobile/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/mobile/android/app/build.gradle.kts b/mobile/android/app/build.gradle.kts new file mode 100644 index 0000000..ff694fb --- /dev/null +++ b/mobile/android/app/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.pleasepayme.please_pay_me" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.pleasepayme.please_pay_me" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + // Uses the version code from pubspec.yaml. When using split APKs, 1000 * ABI_VERSION + // is added automatically by Flutter. (https://developer.android.com/studio/build/configure-apk-splits#configure-APK-versions) + // You can force using the value of versionCode by specifying the `-P force-version-code-ignoring-abi=true` + // flag during build. + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/mobile/android/app/src/debug/AndroidManifest.xml b/mobile/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/mobile/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2acf4be --- /dev/null +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/app/src/main/kotlin/com/pleasepayme/please_pay_me/MainActivity.kt b/mobile/android/app/src/main/kotlin/com/pleasepayme/please_pay_me/MainActivity.kt new file mode 100644 index 0000000..fbffd1d --- /dev/null +++ b/mobile/android/app/src/main/kotlin/com/pleasepayme/please_pay_me/MainActivity.kt @@ -0,0 +1,5 @@ +package com.pleasepayme.please_pay_me + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..5d9ced2 Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/mobile/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/mobile/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..e2e725a Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/mobile/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..9088876 --- /dev/null +++ b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/mobile/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..5ef9895 Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/mobile/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..1f8cf41 Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6262877 Binary files /dev/null and b/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/mobile/android/app/src/main/res/drawable/ic_splash_mark.xml b/mobile/android/app/src/main/res/drawable/ic_splash_mark.xml new file mode 100644 index 0000000..a7571aa --- /dev/null +++ b/mobile/android/app/src/main/res/drawable/ic_splash_mark.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/mobile/android/app/src/main/res/drawable/ic_splash_ruble.xml b/mobile/android/app/src/main/res/drawable/ic_splash_ruble.xml new file mode 100644 index 0000000..d048f97 --- /dev/null +++ b/mobile/android/app/src/main/res/drawable/ic_splash_ruble.xml @@ -0,0 +1,10 @@ + + + + diff --git a/mobile/android/app/src/main/res/drawable/launch_background.xml b/mobile/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..9088876 --- /dev/null +++ b/mobile/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..c79c58a --- /dev/null +++ b/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..eb50e08 Binary files /dev/null and b/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..753b3ab Binary files /dev/null and b/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..890602b Binary files /dev/null and b/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..8f1e5ea Binary files /dev/null and b/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..49b122e Binary files /dev/null and b/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/mobile/android/app/src/main/res/values-night-v31/styles.xml b/mobile/android/app/src/main/res/values-night-v31/styles.xml new file mode 100644 index 0000000..f91628a --- /dev/null +++ b/mobile/android/app/src/main/res/values-night-v31/styles.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/mobile/android/app/src/main/res/values-night/colors.xml b/mobile/android/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..5f41835 --- /dev/null +++ b/mobile/android/app/src/main/res/values-night/colors.xml @@ -0,0 +1,5 @@ + + + #000000 + #3CD68C + diff --git a/mobile/android/app/src/main/res/values-night/styles.xml b/mobile/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..7319b6a --- /dev/null +++ b/mobile/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/mobile/android/app/src/main/res/values-v31/styles.xml b/mobile/android/app/src/main/res/values-v31/styles.xml new file mode 100644 index 0000000..4588b82 --- /dev/null +++ b/mobile/android/app/src/main/res/values-v31/styles.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/mobile/android/app/src/main/res/values/colors.xml b/mobile/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..e4adee3 --- /dev/null +++ b/mobile/android/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #F2F2F7 + #12885A + #12885A + \ No newline at end of file diff --git a/mobile/android/app/src/main/res/values/strings.xml b/mobile/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..3612387 --- /dev/null +++ b/mobile/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Дожить до ЗП + diff --git a/mobile/android/app/src/main/res/values/styles.xml b/mobile/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..d4191b7 --- /dev/null +++ b/mobile/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/android/app/src/profile/AndroidManifest.xml b/mobile/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/mobile/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile/android/build.gradle.kts b/mobile/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/mobile/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/mobile/android/gradle.properties b/mobile/android/gradle.properties new file mode 100644 index 0000000..e96108c --- /dev/null +++ b/mobile/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a20f2c4 --- /dev/null +++ b/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip diff --git a/mobile/android/settings.gradle.kts b/mobile/android/settings.gradle.kts new file mode 100644 index 0000000..b28021a --- /dev/null +++ b/mobile/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.1.0" apply false + id("org.jetbrains.kotlin.android") version "2.4.0" apply false +} + +include(":app") diff --git a/mobile/assets/branding/app_icon.png b/mobile/assets/branding/app_icon.png new file mode 100644 index 0000000..35bbf19 Binary files /dev/null and b/mobile/assets/branding/app_icon.png differ diff --git a/mobile/assets/branding/rustore_icon_512.png b/mobile/assets/branding/rustore_icon_512.png new file mode 100644 index 0000000..14f4632 Binary files /dev/null and b/mobile/assets/branding/rustore_icon_512.png differ diff --git a/mobile/ios/.gitignore b/mobile/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/mobile/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/mobile/ios/Flutter/AppFrameworkInfo.plist b/mobile/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..391a902 --- /dev/null +++ b/mobile/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/mobile/ios/Flutter/Debug.xcconfig b/mobile/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/mobile/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/mobile/ios/Flutter/Release.xcconfig b/mobile/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/mobile/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a3c2e5e --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,647 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.pleasepayme.pleasePayMe; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.pleasepayme.pleasePayMe.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.pleasepayme.pleasePayMe.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.pleasepayme.pleasePayMe.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.pleasepayme.pleasePayMe; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.pleasepayme.pleasePayMe; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c3fedb2 --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata b/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/mobile/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d0d98aa --- /dev/null +++ b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..177e55b Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..bc581d3 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..964097a Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..d0485d9 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..91efcfb Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..e3a25c6 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..05c41cc Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..964097a Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..287bdd6 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..1114bbf Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000..371ad3d Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 0000000..b77ed8f Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000..399dc98 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000..54001c2 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..1114bbf Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..22e748a Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000..eb50e08 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000..8f1e5ea Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..0f21da7 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..4531266 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..ca2db6a Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/mobile/ios/Runner/Assets.xcassets/SplashBackground.colorset/Contents.json b/mobile/ios/Runner/Assets.xcassets/SplashBackground.colorset/Contents.json new file mode 100644 index 0000000..689b9b2 --- /dev/null +++ b/mobile/ios/Runner/Assets.xcassets/SplashBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.969", + "green" : "0.949", + "red" : "0.949" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard b/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..2143525 --- /dev/null +++ b/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios/Runner/Base.lproj/Main.storyboard b/mobile/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/mobile/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist new file mode 100644 index 0000000..f27a536 --- /dev/null +++ b/mobile/ios/Runner/Info.plist @@ -0,0 +1,77 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Дожить до ЗП + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + please_pay_me + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + LSApplicationQueriesSchemes + + tg + telegram + https + http + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/mobile/ios/Runner/Runner-Bridging-Header.h b/mobile/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/mobile/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/mobile/ios/Runner/SceneDelegate.swift b/mobile/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/mobile/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/mobile/ios/RunnerTests/RunnerTests.swift b/mobile/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/mobile/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/mobile/lib/app/app.dart b/mobile/lib/app/app.dart new file mode 100644 index 0000000..c8c9e33 --- /dev/null +++ b/mobile/lib/app/app.dart @@ -0,0 +1,115 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:please_pay_me/app/home_tabs.dart'; +import 'package:please_pay_me/features/auth/login_screen.dart'; +import 'package:please_pay_me/features/auth/session_controller.dart'; +import 'package:please_pay_me/features/splash/splash_screen.dart'; +import 'package:please_pay_me/features/budgets/budgets_controller.dart'; +import 'package:please_pay_me/features/journal/journal_controller.dart'; +import 'package:please_pay_me/features/work/jobs_controller.dart'; +import 'package:please_pay_me/core/branding/app_brand.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:provider/provider.dart'; + +class PleasePayMeApp extends StatelessWidget { + PleasePayMeApp({super.key, required this.session, ThemeController? theme}) + : theme = theme ?? ThemeController(store: MemoryThemeStore()); + + final SessionController session; + final ThemeController theme; + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: session), + ChangeNotifierProvider.value(value: theme), + ], + child: Consumer( + builder: (context, theme, _) { + final platform = MediaQuery.platformBrightnessOf(context); + final brightness = theme.resolve(platform); + + return CupertinoApp( + title: AppBrand.name, + theme: brightness == Brightness.dark ? buildDarkTheme() : buildLightTheme(), + locale: const Locale('ru'), + supportedLocales: const [Locale('ru'), Locale('en')], + localizationsDelegates: const [ + GlobalCupertinoLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + builder: (context, child) { + final overlay = systemUiOverlayFor(brightness); + SystemChrome.setSystemUIOverlayStyle(overlay); + return AnnotatedRegion( + value: overlay, + child: MediaQuery( + data: MediaQuery.of(context).copyWith(platformBrightness: brightness), + child: child!, + ), + ); + }, + home: const _SessionGate(), + ); + }, + ), + ); + } +} + +class _SessionGate extends StatelessWidget { + const _SessionGate(); + + @override + Widget build(BuildContext context) { + final session = context.watch(); + + return switch (session.status) { + SessionStatus.restoring => const SplashScreen(), + SessionStatus.signedOut => const LoginScreen(), + SessionStatus.signedIn => AppDataScope( + key: ValueKey(session.sessionKey), + session: session, + child: const HomeTabs(), + ), + }; + } +} + +/// Feature controllers bound to the current session. Rebuilt from scratch when +/// the session changes, so no stale data survives a re-login. +class AppDataScope extends StatelessWidget { + const AppDataScope({ + super.key, + required this.session, + required this.child, + }); + + final SessionController session; + final Widget child; + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => BudgetsController( + budgets: session.budgets, + expenses: session.expenses, + )..load(), + ), + ChangeNotifierProvider( + create: (_) => JournalController(expenses: session.expenses)..load(), + ), + ChangeNotifierProvider( + create: (_) => JobsController(jobs: session.jobs)..load(), + ), + ], + child: child, + ); + } +} + diff --git a/mobile/lib/app/home_tabs.dart b/mobile/lib/app/home_tabs.dart new file mode 100644 index 0000000..3de923a --- /dev/null +++ b/mobile/lib/app/home_tabs.dart @@ -0,0 +1,56 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/features/budgets/budgets_screen.dart'; +import 'package:please_pay_me/features/journal/journal_screen.dart'; +import 'package:please_pay_me/features/overview/overview_screen.dart'; +import 'package:please_pay_me/features/profile/profile_screen.dart'; +import 'package:please_pay_me/features/work/work_screen.dart'; +import 'package:please_pay_me/ui/ui.dart'; + +/// Root tab bar. Each tab keeps its own navigator so modal sheets and alerts +/// stay inside the tab, as iOS expects. +class HomeTabs extends StatelessWidget { + const HomeTabs({super.key}); + + static const _tabs = [ + AppTabItem( + icon: CupertinoIcons.chart_pie, + activeIcon: CupertinoIcons.chart_pie_fill, + label: 'Обзор', + ), + AppTabItem( + icon: CupertinoIcons.list_bullet, + label: 'Журнал', + ), + AppTabItem( + icon: CupertinoIcons.money_rubl_circle, + activeIcon: CupertinoIcons.money_rubl_circle_fill, + label: 'Бюджеты', + ), + AppTabItem( + icon: CupertinoIcons.briefcase, + activeIcon: CupertinoIcons.briefcase_fill, + label: 'Работа', + ), + AppTabItem( + icon: CupertinoIcons.person, + activeIcon: CupertinoIcons.person_fill, + label: 'Профиль', + ), + ]; + + @override + Widget build(BuildContext context) { + return CupertinoTabScaffold( + tabBar: AppTabBar(items: _tabs, currentIndex: 0, onTap: (_) {}), + tabBuilder: (context, index) => CupertinoTabView( + builder: (context) => switch (index) { + 0 => const OverviewScreen(), + 1 => const JournalScreen(), + 2 => const BudgetsScreen(), + 3 => const WorkScreen(), + _ => const ProfileScreen(), + }, + ), + ); + } +} diff --git a/mobile/lib/core/branding/app_brand.dart b/mobile/lib/core/branding/app_brand.dart new file mode 100644 index 0000000..4148ec8 --- /dev/null +++ b/mobile/lib/core/branding/app_brand.dart @@ -0,0 +1,4 @@ +/// User-facing product name on the home screen and in the UI. +abstract final class AppBrand { + static const name = 'Дожить до ЗП'; +} diff --git a/mobile/lib/core/config/app_config.dart b/mobile/lib/core/config/app_config.dart new file mode 100644 index 0000000..5a32465 --- /dev/null +++ b/mobile/lib/core/config/app_config.dart @@ -0,0 +1,58 @@ +import 'package:please_pay_me/core/config/env_file.dart'; + +/// Runtime configuration. +/// +/// Values are resolved in this order: +/// 1. `--dart-define=PPM_*` / `--dart-define-from-file=.env` (CI and `run.ps1`) +/// 2. key/value map parsed from `mobile/.env` (tests and explicit loaders) +/// 3. production cabinet if nothing is set +class AppConfig { + static const productionOrigin = 'https://please-pay-me.ru'; + + const AppConfig({ + required this.apiBaseUrl, + required this.webCabinetUrl, + this.demoMode = false, + }); + + factory AppConfig.fromEnvironment({Map file = const {}}) { + const definedApi = String.fromEnvironment('PPM_API_BASE_URL'); + const definedWeb = String.fromEnvironment('PPM_WEB_URL'); + const definedDemo = String.fromEnvironment('PPM_DEMO'); + + return AppConfig.fromMap({ + ...file, + if (definedApi.isNotEmpty) 'PPM_API_BASE_URL': definedApi, + if (definedWeb.isNotEmpty) 'PPM_WEB_URL': definedWeb, + if (definedDemo.isNotEmpty) 'PPM_DEMO': definedDemo, + }); + } + + factory AppConfig.fromMap(Map values) { + final apiBase = normalizeUrl(values['PPM_API_BASE_URL'] ?? ''); + final webUrl = normalizeUrl(values['PPM_WEB_URL'] ?? ''); + final demoForced = parseEnvFlag(values['PPM_DEMO']); + final resolvedApi = apiBase.isEmpty ? productionOrigin : apiBase; + final resolvedWeb = webUrl.isEmpty ? resolvedApi : webUrl; + + return AppConfig( + apiBaseUrl: resolvedApi, + webCabinetUrl: resolvedWeb, + demoMode: demoForced, + ); + } + + static const demo = AppConfig(apiBaseUrl: '', webCabinetUrl: '', demoMode: true); + + final String apiBaseUrl; + final String webCabinetUrl; + final bool demoMode; + + AppConfig copyWith({String? apiBaseUrl, String? webCabinetUrl, bool? demoMode}) { + return AppConfig( + apiBaseUrl: apiBaseUrl ?? this.apiBaseUrl, + webCabinetUrl: webCabinetUrl ?? this.webCabinetUrl, + demoMode: demoMode ?? this.demoMode, + ); + } +} diff --git a/mobile/lib/core/config/env_file.dart b/mobile/lib/core/config/env_file.dart new file mode 100644 index 0000000..ffbe9fe --- /dev/null +++ b/mobile/lib/core/config/env_file.dart @@ -0,0 +1,41 @@ +/// Minimal `.env` parser (KEY=VALUE, `#` comments, optional quotes). +/// +/// Kept tiny and dependency-free so config loading is easy to test and does +/// not pull `flutter_dotenv` into the production graph. +Map parseEnvFile(String source) { + final values = {}; + + for (final raw in source.split(RegExp(r'\r?\n'))) { + final line = raw.trim(); + if (line.isEmpty || line.startsWith('#')) continue; + + final separator = line.indexOf('='); + if (separator <= 0) continue; + + final key = line.substring(0, separator).trim(); + if (key.isEmpty) continue; + + var value = line.substring(separator + 1).trim(); + if (value.length >= 2) { + final quote = value[0]; + if ((quote == '"' || quote == "'") && value.endsWith(quote)) { + value = value.substring(1, value.length - 1); + } + } + + values[key] = value; + } + + return values; +} + +String normalizeUrl(String url) => url.trim().replaceAll(RegExp(r'/$'), ''); + +bool parseEnvFlag(String? raw, {bool fallback = false}) { + if (raw == null || raw.trim().isEmpty) return fallback; + return switch (raw.trim().toLowerCase()) { + '1' || 'true' || 'yes' || 'on' => true, + '0' || 'false' || 'no' || 'off' => false, + _ => fallback, + }; +} diff --git a/mobile/lib/core/config/env_loader.dart b/mobile/lib/core/config/env_loader.dart new file mode 100644 index 0000000..977f3d3 --- /dev/null +++ b/mobile/lib/core/config/env_loader.dart @@ -0,0 +1,3 @@ +import 'env_loader_stub.dart' if (dart.library.io) 'env_loader_io.dart' as impl; + +Future> loadEnvFile() => impl.loadEnvFileImpl(); diff --git a/mobile/lib/core/config/env_loader_io.dart b/mobile/lib/core/config/env_loader_io.dart new file mode 100644 index 0000000..8ff3e13 --- /dev/null +++ b/mobile/lib/core/config/env_loader_io.dart @@ -0,0 +1,15 @@ +import 'dart:io'; + +import 'package:please_pay_me/core/config/env_file.dart'; + +/// Reads `mobile/.env` when the process cwd is the package or the repo root. +/// Dart-defines from `run.ps1` still win in [AppConfig.fromEnvironment]. +Future> loadEnvFileImpl() async { + for (final path in const ['.env', 'mobile/.env']) { + final file = File(path); + if (await file.exists()) { + return parseEnvFile(await file.readAsString()); + } + } + return const {}; +} diff --git a/mobile/lib/core/config/env_loader_stub.dart b/mobile/lib/core/config/env_loader_stub.dart new file mode 100644 index 0000000..05027e7 --- /dev/null +++ b/mobile/lib/core/config/env_loader_stub.dart @@ -0,0 +1 @@ +Future> loadEnvFileImpl() async => const {}; diff --git a/mobile/lib/core/format/formatters.dart b/mobile/lib/core/format/formatters.dart new file mode 100644 index 0000000..5d6df1b --- /dev/null +++ b/mobile/lib/core/format/formatters.dart @@ -0,0 +1,60 @@ +import 'package:intl/intl.dart'; + +/// `1 234,50 ₽` — same shape as the web cabinet. +String formatMoney(double amount, {String currency = 'RUB', bool compact = false}) { + final symbol = switch (currency) { + 'RUB' => '₽', + 'USD' => r'$', + 'EUR' => '€', + _ => currency, + }; + + final formatter = compact + ? NumberFormat.decimalPattern('ru') + : NumberFormat('#,##0.00', 'ru'); + final value = compact ? amount.round() : amount; + + return '${formatter.format(value)} $symbol'.replaceAll('\u00A0', ' '); +} + +String formatSignedMoney(double amount, {String currency = 'RUB'}) { + final sign = amount < 0 ? '+' : '−'; + return '$sign${formatMoney(amount.abs(), currency: currency)}'; +} + +String formatDay(DateTime date) => DateFormat('d MMMM', 'ru').format(date); + +String formatShortDate(DateTime date) => DateFormat('dd.MM.yyyy').format(date); + +String formatWeekday(DateTime date) => DateFormat('EEEE', 'ru').format(date); + +/// `Сегодня` / `Вчера` / `12 сентября` — headers of the journal. +String formatRelativeDay(DateTime date, {DateTime? now}) { + final today = _dayOf(now ?? DateTime.now()); + final day = _dayOf(date); + final diff = today.difference(day).inDays; + + return switch (diff) { + 0 => 'Сегодня', + 1 => 'Вчера', + _ => formatDay(day), + }; +} + +/// `осталось 5 дней` — Russian plural rules. +String formatDaysLeft(int days) { + if (days <= 0) return 'период завершён'; + return 'осталось ${plural(days, 'день', 'дня', 'дней')}'; +} + +String plural(int count, String one, String few, String many) { + final mod100 = count % 100; + final mod10 = count % 10; + + if (mod100 >= 11 && mod100 <= 14) return '$count $many'; + if (mod10 == 1) return '$count $one'; + if (mod10 >= 2 && mod10 <= 4) return '$count $few'; + return '$count $many'; +} + +DateTime _dayOf(DateTime value) => DateTime(value.year, value.month, value.day); diff --git a/mobile/lib/core/legal/legal_links.dart b/mobile/lib/core/legal/legal_links.dart new file mode 100644 index 0000000..21ede65 --- /dev/null +++ b/mobile/lib/core/legal/legal_links.dart @@ -0,0 +1,24 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/config/app_config.dart'; +import 'package:please_pay_me/core/config/env_file.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:url_launcher/url_launcher.dart'; + +abstract final class LegalLinks { + static const offer = '/legal/offer'; + static const privacy = '/legal/privacy'; + static const consent = '/legal/consent'; + static const cookies = '/legal/cookies'; + + static Uri resolve(String cabinetUrl, String path) { + final base = cabinetUrl.isEmpty ? AppConfig.productionOrigin : cabinetUrl; + return Uri.parse('${normalizeUrl(base)}$path'); + } +} + +Future openLegalDocument(BuildContext context, Uri uri) async { + final opened = await launchUrl(uri, mode: LaunchMode.externalApplication); + if (!opened && context.mounted) { + await showAppToast(context, message: 'Не удалось открыть документ'); + } +} diff --git a/mobile/lib/core/state/async_value.dart b/mobile/lib/core/state/async_value.dart new file mode 100644 index 0000000..e046883 --- /dev/null +++ b/mobile/lib/core/state/async_value.dart @@ -0,0 +1,41 @@ +/// Minimal async state container so screens can pattern-match over +/// loading / data / error instead of juggling three nullable fields. +sealed class AsyncValue { + const AsyncValue(); + + const factory AsyncValue.loading() = AsyncLoading; + const factory AsyncValue.data(T value) = AsyncData; + const factory AsyncValue.error(String message) = AsyncError; + + T? get valueOrNull => this is AsyncData ? (this as AsyncData).value : null; + + bool get isLoading => this is AsyncLoading; + + R map({ + required R Function() loading, + required R Function(T value) data, + required R Function(String message) error, + }) { + return switch (this) { + AsyncLoading() => loading(), + AsyncData(value: final v) => data(v), + AsyncError(message: final m) => error(m), + }; + } +} + +final class AsyncLoading extends AsyncValue { + const AsyncLoading(); +} + +final class AsyncData extends AsyncValue { + const AsyncData(this.value); + + final T value; +} + +final class AsyncError extends AsyncValue { + const AsyncError(this.message); + + final String message; +} diff --git a/mobile/lib/core/storage/session_storage.dart b/mobile/lib/core/storage/session_storage.dart new file mode 100644 index 0000000..2892a57 --- /dev/null +++ b/mobile/lib/core/storage/session_storage.dart @@ -0,0 +1,78 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +/// Persisted session: JWT, API address and the cached user profile. +abstract interface class SessionStorage { + Future> readAll(); + + Future write({ + required String token, + required String baseUrl, + required String user, + }); + + Future clear(); +} + +class PrefsSessionStorage implements SessionStorage { + const PrefsSessionStorage(); + + static const _tokenKey = 'ppm_token'; + static const _baseUrlKey = 'ppm_base_url'; + static const _userKey = 'ppm_user'; + + @override + Future> readAll() async { + final prefs = await SharedPreferences.getInstance(); + return { + 'token': prefs.getString(_tokenKey), + 'baseUrl': prefs.getString(_baseUrlKey), + 'user': prefs.getString(_userKey), + }; + } + + @override + Future write({ + required String token, + required String baseUrl, + required String user, + }) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_tokenKey, token); + await prefs.setString(_baseUrlKey, baseUrl); + await prefs.setString(_userKey, user); + } + + @override + Future clear() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_tokenKey); + await prefs.remove(_baseUrlKey); + await prefs.remove(_userKey); + } +} + +/// Used by tests and previews — no platform channels involved. +class InMemorySessionStorage implements SessionStorage { + InMemorySessionStorage([Map? initial]) + : _values = {...?initial}; + + final Map _values; + + @override + Future> readAll() async => Map.of(_values); + + @override + Future write({ + required String token, + required String baseUrl, + required String user, + }) async { + _values + ..['token'] = token + ..['baseUrl'] = baseUrl + ..['user'] = user; + } + + @override + Future clear() async => _values.clear(); +} diff --git a/mobile/lib/data/api/api_client.dart b/mobile/lib/data/api/api_client.dart new file mode 100644 index 0000000..8a51610 --- /dev/null +++ b/mobile/lib/data/api/api_client.dart @@ -0,0 +1,124 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:please_pay_me/data/models/json.dart'; + +class ApiException implements Exception { + const ApiException(this.message, {this.statusCode}); + + final String message; + final int? statusCode; + + bool get isUnauthorized => statusCode == 401; + + @override + String toString() => message; +} + +/// Thin JSON transport over the PleasePayMe REST API. +/// +/// Keeps auth concerns out of repositories: the token is supplied lazily so a +/// re-login does not require rebuilding the whole object graph. +class ApiClient { + ApiClient({ + required String baseUrl, + required String? Function() tokenProvider, + http.Client? httpClient, + this.onUnauthorized, + this.timeout = const Duration(seconds: 15), + }) : _baseUrl = baseUrl.replaceAll(RegExp(r'/$'), ''), + _tokenProvider = tokenProvider, + _http = httpClient ?? http.Client(); + + final String _baseUrl; + final String? Function() _tokenProvider; + final http.Client _http; + final void Function()? onUnauthorized; + final Duration timeout; + + Future> getJson(String path, {Map? query}) async { + return asMap(await _send('GET', path, query: query)); + } + + Future> postJson( + String path, { + Map? body, + Map? query, + }) async { + return asMap(await _send('POST', path, body: body, query: query)); + } + + Future> putJson(String path, {Map? body}) async { + return asMap(await _send('PUT', path, body: body)); + } + + Future> patchJson(String path, {Map? body}) async { + return asMap(await _send('PATCH', path, body: body)); + } + + Future> deleteJson(String path, {Map? query}) async { + return asMap(await _send('DELETE', path, query: query)); + } + + Future _send( + String method, + String path, { + Map? body, + Map? query, + }) async { + final uri = Uri.parse('$_baseUrl$path').replace( + queryParameters: query?.isEmpty ?? true ? null : query, + ); + + final request = http.Request(method, uri); + request.headers['Accept'] = 'application/json'; + final token = _tokenProvider(); + if (token != null && token.isNotEmpty) { + request.headers['Authorization'] = 'Bearer $token'; + } + if (body != null) { + request.headers['Content-Type'] = 'application/json'; + request.body = jsonEncode(body); + } + + late final http.Response response; + try { + final streamed = await _http.send(request).timeout(timeout); + response = await http.Response.fromStream(streamed); + } on Exception catch (error) { + throw ApiException('Нет связи с сервером: $error'); + } + + if (response.statusCode == 401) { + onUnauthorized?.call(); + throw const ApiException('Сессия истекла, войдите заново', statusCode: 401); + } + + final raw = utf8.decode(response.bodyBytes); + + if (response.statusCode >= 400) { + throw ApiException(_extractError(raw, response.statusCode), statusCode: response.statusCode); + } + + if (response.statusCode == 204 || raw.trim().isEmpty) return null; + + try { + return jsonDecode(raw); + } on FormatException { + throw ApiException('Сервер вернул не JSON (HTTP ${response.statusCode})'); + } + } + + String _extractError(String raw, int statusCode) { + try { + final parsed = asMap(jsonDecode(raw)); + final detail = asStringOrNull(parsed['detail']) ?? asStringOrNull(parsed['title']); + if (detail != null) return detail; + } on FormatException { + // Fall through to the raw payload. + } + return raw.trim().isEmpty ? 'Ошибка запроса (HTTP $statusCode)' : raw.trim(); + } + + void close() => _http.close(); +} diff --git a/mobile/lib/data/demo/demo_backend.dart b/mobile/lib/data/demo/demo_backend.dart new file mode 100644 index 0000000..75b953d --- /dev/null +++ b/mobile/lib/data/demo/demo_backend.dart @@ -0,0 +1,413 @@ +import 'package:please_pay_me/data/api/api_client.dart'; +import 'package:please_pay_me/data/models/auth_user.dart'; +import 'package:please_pay_me/data/models/budget.dart'; +import 'package:please_pay_me/data/models/expense.dart'; +import 'package:please_pay_me/data/models/job.dart'; +import 'package:please_pay_me/data/repositories/repositories.dart'; + +/// In-memory backend used for Widgetbook previews, widget tests and for +/// running the app without a server (`PPM_DEMO=true`). +/// +/// Mirrors the envelope math of `IBudgetService` closely enough that screens +/// behave the same as against the real API. +class DemoBackend { + DemoBackend({DateTime? today, bool seed = true}) + : _today = _dayOf(today ?? DateTime.now()) { + if (seed) _seed(); + } + + /// Backend without any data — used for empty-state previews and tests. + factory DemoBackend.empty({DateTime? today}) => + DemoBackend(today: today, seed: false); + + final DateTime _today; + final List _budgets = []; + final List _expenses = []; + final List _jobs = []; + + int _selectedBudgetId = 1; + int _nextExpenseId = 100; + int _nextBudgetId = 3; + int _nextJobId = 2; + + static const user = AuthUser( + userId: 1, + firstName: 'Владимир', + username: 'pleasepayme', + ); + + BudgetRepository get budgets => _DemoBudgetRepository(this); + + ExpenseRepository get expenses => _DemoExpenseRepository(this); + + JobRepository get jobs => _DemoJobRepository(this); + + UserRepository get users => _DemoUserRepository(); + + void _seed() { + _budgets.addAll([ + Budget( + id: 1, + userId: 1, + name: 'До аванса', + totalAmount: 42000, + startDate: _today.subtract(const Duration(days: 6)), + endDate: _today.add(const Duration(days: 8)), + currency: 'RUB', + isActive: true, + ), + Budget( + id: 2, + userId: 1, + name: 'Отпуск', + totalAmount: 90000, + startDate: _today.subtract(const Duration(days: 40)), + endDate: _today.subtract(const Duration(days: 5)), + currency: 'RUB', + isActive: false, + ), + ]); + + _expenses.addAll([ + Expense(id: 1, budgetId: 1, amount: 1840, note: 'Продукты', spentAt: _today), + Expense(id: 2, budgetId: 1, amount: 250, note: 'Кофе', spentAt: _today), + Expense( + id: 3, + budgetId: 1, + amount: 640, + note: 'Такси', + spentAt: _today.subtract(const Duration(days: 1)), + ), + Expense( + id: 4, + budgetId: 1, + amount: 3200, + note: 'Аптека', + spentAt: _today.subtract(const Duration(days: 2)), + ), + Expense( + id: 5, + budgetId: 2, + amount: 15000, + note: 'Билеты', + spentAt: _today.subtract(const Duration(days: 20)), + ), + ]); + + _jobs.add( + Job( + id: 1, + userId: 1, + name: 'Основная работа', + salaryAmount: 180000, + currency: 'RUB', + payDays: const [5, 20], + firstPayPercent: 40, + weekendPolicy: WeekendPolicy.beforeWeekend, + isActive: true, + nextPays: [ + UpcomingPay( + date: _today.add(const Duration(days: 8)), + scheduledDay: 20, + percent: 60, + amount: 108000, + ), + UpcomingPay( + date: _today.add(const Duration(days: 23)), + scheduledDay: 5, + percent: 40, + amount: 72000, + ), + ], + ), + ); + } + + Budget _budgetById(int? id) { + if (_budgets.isEmpty) { + throw const ApiException('Сначала создайте бюджет'); + } + final budgetId = id ?? _selectedBudgetId; + return _budgets.firstWhere( + (budget) => budget.id == budgetId, + orElse: () => _budgets.first, + ); + } + + BudgetStatus statusOf(Budget budget) { + final spent = _expenses + .where((expense) => expense.budgetId == budget.id) + .fold(0, (sum, expense) => sum + expense.amount); + final spentToday = _expenses + .where((e) => e.budgetId == budget.id && _dayOf(e.spentAt) == _today) + .fold(0, (sum, expense) => sum + expense.amount); + + final daysLeft = budget.endDate.difference(_today).inDays + 1; + final safeDays = daysLeft < 1 ? 0 : daysLeft; + final remaining = budget.totalAmount - spent; + final dailyLimit = safeDays == 0 ? 0.0 : (remaining <= 0 ? 0.0 : remaining / safeDays); + + return BudgetStatus( + budget: budget, + today: _today, + daysLeft: safeDays, + totalSpent: spent, + remaining: remaining, + dailyLimit: dailyLimit, + spentToday: spentToday, + remainingToday: dailyLimit - spentToday, + isOverDaily: spentToday > dailyLimit, + isOverBudget: remaining < 0, + isExpired: safeDays == 0, + selected: budget.id == _selectedBudgetId, + ); + } + + static DateTime _dayOf(DateTime value) => DateTime(value.year, value.month, value.day); +} + +class _DemoBudgetRepository implements BudgetRepository { + const _DemoBudgetRepository(this._backend); + + final DemoBackend _backend; + + @override + Future> list() async { + return _backend._budgets.map(_backend.statusOf).toList() + ..sort((a, b) { + if (a.selected != b.selected) return a.selected ? -1 : 1; + return b.budget.endDate.compareTo(a.budget.endDate); + }); + } + + @override + Future status({int? budgetId}) async { + return _backend.statusOf(_backend._budgetById(budgetId)); + } + + @override + Future create({ + required String name, + required double totalAmount, + required DateTime endDate, + DateTime? startDate, + }) async { + final budget = Budget( + id: _backend._nextBudgetId++, + userId: 1, + name: name, + totalAmount: totalAmount, + startDate: startDate ?? _backend._today, + endDate: endDate, + currency: 'RUB', + isActive: true, + ); + _backend._budgets.add(budget); + _backend._selectedBudgetId = budget.id; + return _backend.statusOf(budget); + } + + @override + Future update({ + required int budgetId, + String? name, + double? totalAmount, + DateTime? endDate, + DateTime? startDate, + bool resetExpenses = false, + }) async { + final index = _backend._budgets.indexWhere((budget) => budget.id == budgetId); + final current = _backend._budgets[index]; + final updated = Budget( + id: current.id, + userId: current.userId, + name: name ?? current.name, + totalAmount: totalAmount ?? current.totalAmount, + startDate: startDate ?? current.startDate, + endDate: endDate ?? current.endDate, + currency: current.currency, + isActive: current.isActive, + ); + _backend._budgets[index] = updated; + if (resetExpenses) { + _backend._expenses.removeWhere((expense) => expense.budgetId == budgetId); + } + return _backend.statusOf(updated); + } + + @override + Future select(int budgetId) async { + _backend._selectedBudgetId = budgetId; + return _backend.statusOf(_backend._budgetById(budgetId)); + } + + @override + Future setActive(int budgetId, {required bool isActive}) async { + final index = _backend._budgets.indexWhere((budget) => budget.id == budgetId); + final current = _backend._budgets[index]; + final updated = Budget( + id: current.id, + userId: current.userId, + name: current.name, + totalAmount: current.totalAmount, + startDate: current.startDate, + endDate: current.endDate, + currency: current.currency, + isActive: isActive, + ); + _backend._budgets[index] = updated; + return _backend.statusOf(updated); + } + + @override + Future delete(int budgetId) async { + _backend._budgets.removeWhere((budget) => budget.id == budgetId); + _backend._expenses.removeWhere((expense) => expense.budgetId == budgetId); + if (_backend._selectedBudgetId == budgetId && _backend._budgets.isNotEmpty) { + _backend._selectedBudgetId = _backend._budgets.first.id; + } + } +} + +class _DemoExpenseRepository implements ExpenseRepository { + const _DemoExpenseRepository(this._backend); + + final DemoBackend _backend; + + @override + Future page({ + required int page, + int pageSize = 20, + int? budgetId, + bool all = false, + }) async { + final scope = all + ? _backend._expenses + : _backend._expenses + .where((e) => e.budgetId == (budgetId ?? _backend._selectedBudgetId)); + + final sorted = scope.toList() + ..sort((a, b) { + final byDate = b.spentAt.compareTo(a.spentAt); + return byDate != 0 ? byDate : b.id.compareTo(a.id); + }); + + final from = (page - 1) * pageSize; + final items = from >= sorted.length + ? [] + : sorted.sublist(from, (from + pageSize).clamp(0, sorted.length)); + + return ExpensesPage( + page: page, + totalPages: sorted.isEmpty ? 1 : (sorted.length / pageSize).ceil(), + totalCount: sorted.length, + pageSize: pageSize, + totalSum: sorted.fold(0, (sum, expense) => sum + expense.amount), + budgetId: all ? null : (budgetId ?? _backend._selectedBudgetId), + items: items, + ); + } + + @override + Future create({ + required double amount, + String? note, + DateTime? spentAt, + int? budgetId, + }) async { + final budget = _backend._budgetById(budgetId); + _backend._expenses.add( + Expense( + id: _backend._nextExpenseId++, + budgetId: budget.id, + amount: amount, + note: note, + spentAt: spentAt ?? _backend._today, + ), + ); + return _backend.statusOf(budget); + } + + @override + Future undoLast({int? budgetId}) async { + final budget = _backend._budgetById(budgetId); + final scoped = _backend._expenses.where((e) => e.budgetId == budget.id).toList(); + if (scoped.isEmpty) return 0; + + scoped.sort((a, b) => b.id.compareTo(a.id)); + final last = scoped.first; + _backend._expenses.removeWhere((expense) => expense.id == last.id); + return last.amount; + } +} + +class _DemoJobRepository implements JobRepository { + const _DemoJobRepository(this._backend); + + final DemoBackend _backend; + + @override + Future> list() async => List.unmodifiable(_backend._jobs); + + @override + Future create({ + required String name, + required double salaryAmount, + required List payDays, + required double firstPayPercent, + required WeekendPolicy weekendPolicy, + }) async { + final job = Job( + id: _backend._nextJobId++, + userId: 1, + name: name, + salaryAmount: salaryAmount, + currency: 'RUB', + payDays: payDays, + firstPayPercent: firstPayPercent, + weekendPolicy: weekendPolicy, + isActive: true, + nextPays: const [], + ); + _backend._jobs.add(job); + return job; + } + + @override + Future update({ + required int jobId, + required String name, + required double salaryAmount, + required List payDays, + required double firstPayPercent, + required WeekendPolicy weekendPolicy, + bool isActive = true, + }) async { + final index = _backend._jobs.indexWhere((job) => job.id == jobId); + final current = _backend._jobs[index]; + final updated = Job( + id: current.id, + userId: current.userId, + name: name, + salaryAmount: salaryAmount, + currency: current.currency, + payDays: payDays, + firstPayPercent: firstPayPercent, + weekendPolicy: weekendPolicy, + isActive: isActive, + nextPays: current.nextPays, + ); + _backend._jobs[index] = updated; + return updated; + } + + @override + Future delete(int jobId) async { + _backend._jobs.removeWhere((job) => job.id == jobId); + } +} + +class _DemoUserRepository implements UserRepository { + @override + Future me() async => DemoBackend.user; +} diff --git a/mobile/lib/data/models/auth_user.dart b/mobile/lib/data/models/auth_user.dart new file mode 100644 index 0000000..1b10120 --- /dev/null +++ b/mobile/lib/data/models/auth_user.dart @@ -0,0 +1,79 @@ +import 'dart:convert'; + +import 'package:please_pay_me/data/models/json.dart'; + +class AuthUser { + const AuthUser({ + required this.userId, + this.firstName, + this.lastName, + this.username, + this.photoUrl, + }); + + factory AuthUser.fromJson(Map json) { + return AuthUser( + userId: asInt(json['user_id']), + firstName: asStringOrNull(json['first_name']), + lastName: asStringOrNull(json['last_name']), + username: asStringOrNull(json['username']), + photoUrl: asStringOrNull(json['photo_url']), + ); + } + + static AuthUser? tryDecode(String? raw) { + if (raw == null || raw.isEmpty) return null; + try { + return AuthUser.fromJson(asMap(jsonDecode(raw))); + } on FormatException { + return null; + } + } + + final int userId; + final String? firstName; + final String? lastName; + final String? username; + final String? photoUrl; + + String get displayName { + final full = [firstName, lastName].whereType().join(' ').trim(); + if (full.isNotEmpty) return full; + if (username != null) return '@$username'; + return 'Пользователь $userId'; + } + + String get handle => username != null ? '@$username' : 'id $userId'; + + String get initials { + final source = firstName?.trim().isNotEmpty == true + ? firstName!.trim() + : username?.trim() ?? ''; + if (source.isEmpty) return ''; + return source.substring(0, 1); + } + + Map toJson() => { + 'user_id': userId, + 'first_name': firstName, + 'last_name': lastName, + 'username': username, + 'photo_url': photoUrl, + }; + + String encode() => jsonEncode(toJson()); +} + +class AuthSession { + const AuthSession({required this.accessToken, required this.user}); + + factory AuthSession.fromJson(Map json) { + return AuthSession( + accessToken: asString(json['access_token']), + user: AuthUser.fromJson(asMap(json['user'])), + ); + } + + final String accessToken; + final AuthUser user; +} diff --git a/mobile/lib/data/models/budget.dart b/mobile/lib/data/models/budget.dart new file mode 100644 index 0000000..48750dc --- /dev/null +++ b/mobile/lib/data/models/budget.dart @@ -0,0 +1,108 @@ +import 'package:please_pay_me/data/models/json.dart'; + +class Budget { + const Budget({ + required this.id, + required this.userId, + required this.name, + required this.totalAmount, + required this.startDate, + required this.endDate, + required this.currency, + required this.isActive, + }); + + factory Budget.fromJson(Map json) { + return Budget( + id: asInt(json['id']), + userId: asInt(json['user_id']), + name: asString(json['name']), + totalAmount: asDouble(json['total_amount']), + startDate: asDate(json['start_date']), + endDate: asDate(json['end_date']), + currency: asString(json['currency'], fallback: 'RUB'), + isActive: asBool(json['is_active']), + ); + } + + final int id; + final int userId; + final String name; + final double totalAmount; + final DateTime startDate; + final DateTime endDate; + final String currency; + final bool isActive; +} + +/// Budget plus the server-computed daily envelope math. +class BudgetStatus { + const BudgetStatus({ + required this.budget, + required this.today, + required this.daysLeft, + required this.totalSpent, + required this.remaining, + required this.dailyLimit, + required this.spentToday, + required this.remainingToday, + required this.isOverDaily, + required this.isOverBudget, + required this.isExpired, + required this.selected, + }); + + factory BudgetStatus.fromJson(Map json) { + return BudgetStatus( + budget: Budget.fromJson(asMap(json['budget'])), + today: asDate(json['today']), + daysLeft: asInt(json['days_left']), + totalSpent: asDouble(json['total_spent']), + remaining: asDouble(json['remaining']), + dailyLimit: asDouble(json['daily_limit']), + spentToday: asDouble(json['spent_today']), + remainingToday: asDouble(json['remaining_today']), + isOverDaily: asBool(json['is_over_daily']), + isOverBudget: asBool(json['is_over_budget']), + isExpired: asBool(json['is_expired']), + selected: asBool(json['selected']), + ); + } + + final Budget budget; + final DateTime today; + final int daysLeft; + final double totalSpent; + final double remaining; + final double dailyLimit; + final double spentToday; + final double remainingToday; + final bool isOverDaily; + final bool isOverBudget; + final bool isExpired; + final bool selected; + + /// 0..1 — share of the budget already spent. + double get spentProgress { + if (budget.totalAmount <= 0) return 0; + return (totalSpent / budget.totalAmount).clamp(0.0, 1.0); + } + + /// 0..1 — share of today's envelope already spent. + double get dailyProgress { + if (dailyLimit <= 0) return spentToday > 0 ? 1 : 0; + return (spentToday / dailyLimit).clamp(0.0, 1.0); + } +} + +class BudgetsList { + const BudgetsList({required this.items}); + + factory BudgetsList.fromJson(Map json) { + return BudgetsList( + items: asList(json['items']).map(BudgetStatus.fromJson).toList(), + ); + } + + final List items; +} diff --git a/mobile/lib/data/models/expense.dart b/mobile/lib/data/models/expense.dart new file mode 100644 index 0000000..a8c7fe5 --- /dev/null +++ b/mobile/lib/data/models/expense.dart @@ -0,0 +1,82 @@ +import 'package:please_pay_me/data/models/json.dart'; + +class Expense { + const Expense({ + required this.id, + required this.budgetId, + required this.amount, + required this.spentAt, + this.note, + }); + + factory Expense.fromJson(Map json) { + return Expense( + id: asInt(json['id']), + budgetId: asInt(json['budget_id']), + amount: asDouble(json['amount']), + spentAt: asDate(json['spent_at']), + note: asStringOrNull(json['note']), + ); + } + + final int id; + final int budgetId; + final double amount; + final DateTime spentAt; + final String? note; +} + +class ExpensesPage { + const ExpensesPage({ + required this.page, + required this.totalPages, + required this.totalCount, + required this.pageSize, + required this.totalSum, + required this.items, + this.budgetId, + }); + + factory ExpensesPage.fromJson(Map json) { + return ExpensesPage( + page: asInt(json['page'], fallback: 1), + totalPages: asInt(json['total_pages'], fallback: 1), + totalCount: asInt(json['total_count']), + pageSize: asInt(json['page_size'], fallback: 20), + totalSum: asDouble(json['total_sum']), + budgetId: json['budget_id'] == null ? null : asInt(json['budget_id']), + items: asList(json['items']).map(Expense.fromJson).toList(), + ); + } + + static const empty = ExpensesPage( + page: 1, + totalPages: 1, + totalCount: 0, + pageSize: 20, + totalSum: 0, + items: [], + ); + + final int page; + final int totalPages; + final int totalCount; + final int pageSize; + final double totalSum; + final int? budgetId; + final List items; + + bool get hasMore => page < totalPages; + + ExpensesPage copyWithItems(List items, {int? page}) { + return ExpensesPage( + page: page ?? this.page, + totalPages: totalPages, + totalCount: totalCount, + pageSize: pageSize, + totalSum: totalSum, + budgetId: budgetId, + items: items, + ); + } +} diff --git a/mobile/lib/data/models/job.dart b/mobile/lib/data/models/job.dart new file mode 100644 index 0000000..35dd520 --- /dev/null +++ b/mobile/lib/data/models/job.dart @@ -0,0 +1,95 @@ +import 'package:please_pay_me/data/models/json.dart'; + +enum WeekendPolicy { + beforeWeekend('before_weekend', 'До выходных'), + afterWeekend('after_weekend', 'После выходных'); + + const WeekendPolicy(this.wire, this.label); + + factory WeekendPolicy.fromWire(Object? value) { + return WeekendPolicy.values.firstWhere( + (policy) => policy.wire == asString(value), + orElse: () => WeekendPolicy.beforeWeekend, + ); + } + + final String wire; + final String label; +} + +class UpcomingPay { + const UpcomingPay({ + required this.date, + required this.scheduledDay, + required this.percent, + required this.amount, + }); + + factory UpcomingPay.fromJson(Map json) { + return UpcomingPay( + date: asDate(json['date']), + scheduledDay: asInt(json['scheduled_day']), + percent: asDouble(json['percent']), + amount: asDouble(json['amount']), + ); + } + + final DateTime date; + final int scheduledDay; + final double percent; + final double amount; +} + +class Job { + const Job({ + required this.id, + required this.userId, + required this.name, + required this.salaryAmount, + required this.currency, + required this.payDays, + required this.firstPayPercent, + required this.weekendPolicy, + required this.isActive, + required this.nextPays, + }); + + factory Job.fromJson(Map json) { + final rawDays = json['pay_days']; + return Job( + id: asInt(json['id']), + userId: asInt(json['user_id']), + name: asString(json['name']), + salaryAmount: asDouble(json['salary_amount']), + currency: asString(json['currency'], fallback: 'RUB'), + payDays: rawDays is List ? rawDays.map(asInt).toList() : const [], + firstPayPercent: asDouble(json['first_pay_percent']), + weekendPolicy: WeekendPolicy.fromWire(json['weekend_policy']), + isActive: asBool(json['is_active']), + nextPays: asList(json['next_pays']).map(UpcomingPay.fromJson).toList(), + ); + } + + final int id; + final int userId; + final String name; + final double salaryAmount; + final String currency; + final List payDays; + final double firstPayPercent; + final WeekendPolicy weekendPolicy; + final bool isActive; + final List nextPays; + + UpcomingPay? get nextPay => nextPays.isEmpty ? null : nextPays.first; +} + +class JobsList { + const JobsList({required this.items}); + + factory JobsList.fromJson(Map json) { + return JobsList(items: asList(json['items']).map(Job.fromJson).toList()); + } + + final List items; +} diff --git a/mobile/lib/data/models/json.dart b/mobile/lib/data/models/json.dart new file mode 100644 index 0000000..928b359 --- /dev/null +++ b/mobile/lib/data/models/json.dart @@ -0,0 +1,63 @@ +/// Tolerant JSON coercion helpers. +/// +/// The API is generated from C# records, so numbers may arrive as `int` or +/// `double` and nullable strings as `null`; parsing must not crash the UI. +library; + +int asInt(Object? value, {int fallback = 0}) { + if (value is int) return value; + if (value is num) return value.toInt(); + if (value is String) return int.tryParse(value) ?? fallback; + return fallback; +} + +double asDouble(Object? value, {double fallback = 0}) { + if (value is double) return value; + if (value is num) return value.toDouble(); + if (value is String) return double.tryParse(value.replaceAll(',', '.')) ?? fallback; + return fallback; +} + +bool asBool(Object? value, {bool fallback = false}) { + if (value is bool) return value; + if (value is String) return value.toLowerCase() == 'true'; + return fallback; +} + +String asString(Object? value, {String fallback = ''}) { + if (value is String) return value; + if (value == null) return fallback; + return value.toString(); +} + +String? asStringOrNull(Object? value) { + if (value is String && value.isNotEmpty) return value; + return null; +} + +/// Parses `YYYY-MM-DD` (and full ISO timestamps) as a local calendar day. +DateTime asDate(Object? value) { + final raw = asString(value); + if (raw.isEmpty) return DateTime.now(); + final parsed = DateTime.tryParse(raw); + if (parsed == null) return DateTime.now(); + return DateTime(parsed.year, parsed.month, parsed.day); +} + +Map asMap(Object? value) { + if (value is Map) return value; + if (value is Map) return value.cast(); + return const {}; +} + +List> asList(Object? value) { + if (value is! List) return const []; + return value.map(asMap).toList(); +} + +/// `YYYY-MM-DD` — the format every date-typed endpoint expects. +String formatIsoDate(DateTime date) { + final month = date.month.toString().padLeft(2, '0'); + final day = date.day.toString().padLeft(2, '0'); + return '${date.year}-$month-$day'; +} diff --git a/mobile/lib/data/repositories/api_repositories.dart b/mobile/lib/data/repositories/api_repositories.dart new file mode 100644 index 0000000..857befe --- /dev/null +++ b/mobile/lib/data/repositories/api_repositories.dart @@ -0,0 +1,193 @@ +import 'package:please_pay_me/data/api/api_client.dart'; +import 'package:please_pay_me/data/models/auth_user.dart'; +import 'package:please_pay_me/data/models/budget.dart'; +import 'package:please_pay_me/data/models/expense.dart'; +import 'package:please_pay_me/data/models/job.dart'; +import 'package:please_pay_me/data/models/json.dart'; +import 'package:please_pay_me/data/repositories/repositories.dart'; + +class ApiBudgetRepository implements BudgetRepository { + const ApiBudgetRepository(this._client); + + final ApiClient _client; + + @override + Future> list() async { + final json = await _client.getJson('/api/me/budgets'); + return BudgetsList.fromJson(json).items; + } + + @override + Future status({int? budgetId}) async { + final json = await _client.getJson( + '/api/me/budget', + query: {if (budgetId != null) 'budget_id': '$budgetId'}, + ); + return BudgetStatus.fromJson(json); + } + + @override + Future create({ + required String name, + required double totalAmount, + required DateTime endDate, + DateTime? startDate, + }) async { + final json = await _client.postJson('/api/me/budgets', body: { + 'name': name, + 'total_amount': totalAmount, + 'end_date': formatIsoDate(endDate), + 'start_date': startDate == null ? null : formatIsoDate(startDate), + 'is_active': true, + 'select': true, + }); + return BudgetStatus.fromJson(json); + } + + @override + Future update({ + required int budgetId, + String? name, + double? totalAmount, + DateTime? endDate, + DateTime? startDate, + bool resetExpenses = false, + }) async { + final json = await _client.putJson('/api/me/budgets/$budgetId', body: { + if (name != null) 'name': name, + if (totalAmount != null) 'total_amount': totalAmount, + if (endDate != null) 'end_date': formatIsoDate(endDate), + if (startDate != null) 'start_date': formatIsoDate(startDate), + 'reset_expenses': resetExpenses, + }); + return BudgetStatus.fromJson(json); + } + + @override + Future select(int budgetId) async { + final json = await _client.postJson('/api/me/budgets/$budgetId/select'); + return BudgetStatus.fromJson(json); + } + + @override + Future setActive(int budgetId, {required bool isActive}) async { + final json = await _client.patchJson( + '/api/me/budgets/$budgetId/active', + body: {'is_active': isActive}, + ); + return BudgetStatus.fromJson(json); + } + + @override + Future delete(int budgetId) => _client.deleteJson('/api/me/budgets/$budgetId'); +} + +class ApiExpenseRepository implements ExpenseRepository { + const ApiExpenseRepository(this._client); + + final ApiClient _client; + + @override + Future page({ + required int page, + int pageSize = 20, + int? budgetId, + bool all = false, + }) async { + final json = await _client.getJson('/api/me/expenses', query: { + 'page': '$page', + 'page_size': '$pageSize', + if (all) 'all': 'true' else if (budgetId != null) 'budget_id': '$budgetId', + }); + return ExpensesPage.fromJson(json); + } + + @override + Future create({ + required double amount, + String? note, + DateTime? spentAt, + int? budgetId, + }) async { + final json = await _client.postJson('/api/me/expenses', body: { + 'amount': amount, + 'note': note, + 'spent_at': spentAt == null ? null : formatIsoDate(spentAt), + 'budget_id': budgetId, + }); + return BudgetStatus.fromJson(json); + } + + @override + Future undoLast({int? budgetId}) async { + final json = await _client.deleteJson( + '/api/me/expenses/last', + query: {if (budgetId != null) 'budget_id': '$budgetId'}, + ); + return asDouble(json['deleted_amount']); + } +} + +class ApiJobRepository implements JobRepository { + const ApiJobRepository(this._client); + + final ApiClient _client; + + @override + Future> list() async { + final json = await _client.getJson('/api/me/jobs'); + return JobsList.fromJson(json).items; + } + + @override + Future create({ + required String name, + required double salaryAmount, + required List payDays, + required double firstPayPercent, + required WeekendPolicy weekendPolicy, + }) async { + final json = await _client.postJson('/api/me/jobs', body: { + 'name': name, + 'salary_amount': salaryAmount, + 'pay_days': payDays, + 'first_pay_percent': firstPayPercent, + 'weekend_policy': weekendPolicy.wire, + 'is_active': true, + }); + return Job.fromJson(json); + } + + @override + Future update({ + required int jobId, + required String name, + required double salaryAmount, + required List payDays, + required double firstPayPercent, + required WeekendPolicy weekendPolicy, + bool isActive = true, + }) async { + final json = await _client.putJson('/api/me/jobs/$jobId', body: { + 'name': name, + 'salary_amount': salaryAmount, + 'pay_days': payDays, + 'first_pay_percent': firstPayPercent, + 'weekend_policy': weekendPolicy.wire, + 'is_active': isActive, + }); + return Job.fromJson(json); + } + + @override + Future delete(int jobId) => _client.deleteJson('/api/me/jobs/$jobId'); +} + +class ApiUserRepository implements UserRepository { + const ApiUserRepository(this._client); + + final ApiClient _client; + + @override + Future me() async => AuthUser.fromJson(await _client.getJson('/api/me')); +} diff --git a/mobile/lib/data/repositories/repositories.dart b/mobile/lib/data/repositories/repositories.dart new file mode 100644 index 0000000..1cf1536 --- /dev/null +++ b/mobile/lib/data/repositories/repositories.dart @@ -0,0 +1,80 @@ +import 'package:please_pay_me/data/models/auth_user.dart'; +import 'package:please_pay_me/data/models/budget.dart'; +import 'package:please_pay_me/data/models/expense.dart'; +import 'package:please_pay_me/data/models/job.dart'; + +/// Contracts the UI depends on. Implemented by the REST backend and by the +/// in-memory demo backend used for previews and tests. +abstract interface class BudgetRepository { + Future> list(); + + Future status({int? budgetId}); + + Future create({ + required String name, + required double totalAmount, + required DateTime endDate, + DateTime? startDate, + }); + + Future update({ + required int budgetId, + String? name, + double? totalAmount, + DateTime? endDate, + DateTime? startDate, + bool resetExpenses = false, + }); + + Future select(int budgetId); + + Future setActive(int budgetId, {required bool isActive}); + + Future delete(int budgetId); +} + +abstract interface class ExpenseRepository { + Future page({ + required int page, + int pageSize = 20, + int? budgetId, + bool all = false, + }); + + Future create({ + required double amount, + String? note, + DateTime? spentAt, + int? budgetId, + }); + + Future undoLast({int? budgetId}); +} + +abstract interface class JobRepository { + Future> list(); + + Future create({ + required String name, + required double salaryAmount, + required List payDays, + required double firstPayPercent, + required WeekendPolicy weekendPolicy, + }); + + Future update({ + required int jobId, + required String name, + required double salaryAmount, + required List payDays, + required double firstPayPercent, + required WeekendPolicy weekendPolicy, + bool isActive = true, + }); + + Future delete(int jobId); +} + +abstract interface class UserRepository { + Future me(); +} diff --git a/mobile/lib/features/auth/login_screen.dart b/mobile/lib/features/auth/login_screen.dart new file mode 100644 index 0000000..4f0da83 --- /dev/null +++ b/mobile/lib/features/auth/login_screen.dart @@ -0,0 +1,331 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/branding/app_brand.dart'; +import 'package:please_pay_me/data/api/api_client.dart'; +import 'package:please_pay_me/features/auth/session_controller.dart'; +import 'package:please_pay_me/features/auth/telegram_login.dart'; +import 'package:please_pay_me/features/auth/yandex_login.dart'; +import 'package:please_pay_me/features/legal/legal_consent.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:provider/provider.dart'; + +/// Opens the cabinet and returns the harvested JWT. +typedef TelegramLoginLauncher = Future Function( + BuildContext context, + String cabinetUrl, +); + +/// Opens Yandex OAuth and returns the JWT issued by `/api/auth/yandex`. +typedef YandexLoginLauncher = Future Function( + BuildContext context, { + required String clientId, + required String redirectUri, +}); + +/// Sign-in screen. +/// +/// Cabinet / API origin comes from `.env` (`PPM_WEB_URL`, `PPM_API_BASE_URL`). +/// Telegram: cabinet WebView + JWT channel. Yandex: OAuth code in a WebView, +/// exchanged on the API so the client secret never leaves the server. +class LoginScreen extends StatefulWidget { + const LoginScreen({ + super.key, + this.launchTelegramLogin, + this.launchYandexLogin, + }); + + /// Injection point for tests and Widgetbook, where no WebView exists. + final TelegramLoginLauncher? launchTelegramLogin; + final YandexLoginLauncher? launchYandexLogin; + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + final _tokenController = TextEditingController(); + + bool _busy = false; + late bool _tokenMode = !_webLoginAvailable; + YandexAuthProvider? _yandex; + String? _providersError; + LegalAcceptance _legal = const LegalAcceptance(); + + bool get _webLoginAvailable => + widget.launchTelegramLogin != null || isTelegramWebLoginSupported; + + bool get _yandexLoginAvailable => + widget.launchYandexLogin != null || isYandexWebLoginSupported; + + String _cabinetUrl(SessionController session) { + if (session.webCabinetUrl.isNotEmpty) return session.webCabinetUrl; + return session.baseUrl; + } + + String _apiBase(SessionController session) { + return resolveApiBaseUrl( + cabinetUrl: _cabinetUrl(session), + configuredApiBaseUrl: session.configuredApiBaseUrl, + ); + } + + @override + void initState() { + super.initState(); + _loadProviders(); + } + + @override + void dispose() { + _tokenController.dispose(); + super.dispose(); + } + + Future _loadProviders() async { + final session = context.read(); + final apiBase = _apiBase(session); + if (apiBase.isEmpty) { + if (mounted) { + setState(() { + _providersError = 'Не задан PPM_API_BASE_URL в .env'; + }); + } else { + _providersError = 'Не задан PPM_API_BASE_URL в .env'; + } + return; + } + + try { + final providers = await fetchAuthProviders(apiBase, httpClient: session.httpClient); + if (!mounted) return; + setState(() { + _yandex = providers.yandex; + _providersError = providers.yandex?.usable == true + ? null + : 'API не включил Яндекс (нет YANDEX_CLIENT_ID/SECRET на сервере)'; + }); + } on ApiException catch (error) { + if (!mounted) return; + setState(() { + _yandex = null; + _providersError = error.message; + }); + } catch (error) { + if (!mounted) return; + setState(() { + _yandex = null; + _providersError = error.toString(); + }); + } + } + + Future _ensureCabinetUrl(SessionController session) async { + if (_cabinetUrl(session).isNotEmpty) return true; + await showAppToast( + context, + message: 'Задайте PPM_WEB_URL в mobile/.env', + icon: CupertinoIcons.exclamationmark_circle_fill, + ); + return false; + } + + @override + Widget build(BuildContext context) { + final session = context.watch(); + + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + child: SafeArea( + child: ListView( + padding: const EdgeInsets.only(top: AppSpacing.s7, bottom: AppSpacing.s6), + children: [ + const Padding( + padding: EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppIcon( + CupertinoIcons.money_rubl_circle_fill, + size: 48, + color: AppColors.accent, + ), + SizedBox(height: AppSpacing.s3), + AppText.largeTitle(AppBrand.name), + SizedBox(height: AppSpacing.s1), + AppText.subhead( + 'Бюджет от зарплаты до зарплаты. Войдите через Яндекс.', + ), + ], + ), + ), + const SizedBox(height: AppSpacing.s6), + Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + LegalConsentBlock( + value: _legal, + cabinetUrl: _cabinetUrl(session), + onChanged: (next) => setState(() => _legal = next), + ), + const SizedBox(height: AppSpacing.s5), + if (_tokenMode) ...[ + AppTextField( + label: 'Токен доступа', + placeholder: 'eyJhbGciOi…', + controller: _tokenController, + enabled: !_busy, + errorText: session.lastError, + ), + const SizedBox(height: AppSpacing.s5), + AppButton( + label: 'Войти по токену', + loading: _busy, + onPressed: _legal.accepted ? _signInWithToken : null, + ), + ] else ...[ + if (session.lastError != null) ...[ + AppText.footnote(session.lastError!, color: AppColors.systemRed), + const SizedBox(height: AppSpacing.s3), + ], + const SizedBox(height: AppSpacing.s3), + AppButton( + label: 'Войти через Яндекс', + style: AppButtonStyle.gray, + loading: _busy, + onPressed: _legal.accepted ? _signInWithYandex : null, + ), + ], + ], + ), + ), + const SizedBox(height: AppSpacing.s5), + const AppListSection( + header: 'Что умеет', + footer: 'Один кабинет на телефоне и в браузере — бюджет не разъедется.', + children: [ + AppListTile( + leading: AppIcon(CupertinoIcons.calendar, color: AppColors.accent), + title: 'Бюджет от зарплаты до зарплаты', + subtitle: 'Остаток и дневной лимит на каждый день периода', + showChevron: false, + ), + AppListTile( + leading: AppIcon(CupertinoIcons.money_rubl, color: AppColors.accent), + title: 'Траты в один тап', + subtitle: 'Журнал по дням, несколько конвертов параллельно', + showChevron: false, + ), + AppListTile( + leading: AppIcon(CupertinoIcons.briefcase, color: AppColors.accent), + title: 'График выплат', + subtitle: 'Оклад, дни зарплаты и правило выходных', + showChevron: false, + ), + ], + ), + ], + ), + ), + ); + } + + Future _signInWithTelegram() async { + final session = context.read(); + if (!await _ensureCabinetUrl(session)) return; + + final cabinetUrl = _cabinetUrl(session); + final launcher = widget.launchTelegramLogin ?? + (ctx, url) => showTelegramLogin(context: ctx, cabinetUrl: url); + + setState(() => _busy = true); + final token = await launcher(context, cabinetUrl); + + if (!mounted) return; + + if (token == null || token.isEmpty) { + setState(() => _busy = false); + return; + } + + await session.signInWithToken(baseUrl: _apiBase(session), token: token); + if (mounted) setState(() => _busy = false); + } + + Future _signInWithYandex() async { + final session = context.read(); + if (!await _ensureCabinetUrl(session)) return; + + if (_yandex == null || !(_yandex?.usable ?? false)) { + setState(() => _busy = true); + await _loadProviders(); + if (mounted) setState(() => _busy = false); + } + + final provider = _yandex; + if (provider == null || !provider.usable) { + await showAppToast( + context, + message: _providersError ?? 'Вход через Яндекс недоступен', + icon: CupertinoIcons.exclamationmark_circle_fill, + ); + return; + } + + if (!_yandexLoginAvailable) { + await showAppToast( + context, + message: 'Яндекс в приложении работает на телефоне. На Windows откройте кабинет в браузере.', + icon: CupertinoIcons.device_phone_portrait, + ); + return; + } + + final cabinetUrl = _cabinetUrl(session); + final apiBase = _apiBase(session); + final redirectUri = cabinetYandexRedirectUri( + cabinetUrl, + configured: provider.redirectUri, + ); + + setState(() => _busy = true); + + final launcher = widget.launchYandexLogin; + final token = launcher != null + ? await launcher(context, clientId: provider.clientId, redirectUri: redirectUri) + : await showYandexLogin( + context: context, + clientId: provider.clientId, + redirectUri: redirectUri, + exchangeCode: (code) => exchangeYandexCode( + apiBaseUrl: apiBase, + code: code, + redirectUri: redirectUri, + httpClient: session.httpClient, + ), + ); + + if (!mounted) return; + + if (token == null || token.isEmpty) { + setState(() => _busy = false); + return; + } + + await session.signInWithToken(baseUrl: apiBase, token: token); + if (mounted) setState(() => _busy = false); + } + + Future _signInWithToken() async { + final session = context.read(); + if (!await _ensureCabinetUrl(session)) return; + + setState(() => _busy = true); + await session.signInWithToken( + baseUrl: _apiBase(session), + token: _tokenController.text, + ); + if (mounted) setState(() => _busy = false); + } +} diff --git a/mobile/lib/features/auth/session_controller.dart b/mobile/lib/features/auth/session_controller.dart new file mode 100644 index 0000000..d0a7c74 --- /dev/null +++ b/mobile/lib/features/auth/session_controller.dart @@ -0,0 +1,186 @@ +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; +import 'package:please_pay_me/core/config/app_config.dart'; +import 'package:please_pay_me/core/storage/session_storage.dart'; +import 'package:please_pay_me/data/api/api_client.dart'; +import 'package:please_pay_me/data/demo/demo_backend.dart'; +import 'package:please_pay_me/data/models/auth_user.dart'; +import 'package:please_pay_me/data/repositories/api_repositories.dart'; +import 'package:please_pay_me/data/repositories/repositories.dart'; + +enum SessionStatus { restoring, signedOut, signedIn } + +/// Owns authentication and hands out repositories bound to the current +/// session, so the rest of the app never sees tokens or base URLs. +class SessionController extends ChangeNotifier { + SessionController({ + required AppConfig config, + required SessionStorage storage, + http.Client? httpClient, + DemoBackend? demoBackend, + }) : _config = config, + _storage = storage, + _httpClient = httpClient, + _demo = demoBackend ?? DemoBackend(); + + final SessionStorage _storage; + final http.Client? _httpClient; + final DemoBackend _demo; + + AppConfig _config; + SessionStatus _status = SessionStatus.restoring; + AuthUser? _user; + String? _token; + String _baseUrl = ''; + bool _isDemo = false; + String? _lastError; + + SessionStatus get status => _status; + AuthUser? get user => _user; + String get baseUrl => _baseUrl.isEmpty ? _config.apiBaseUrl : _baseUrl; + String get webCabinetUrl => _config.webCabinetUrl; + + /// API address baked in at build time; empty when it must be derived from + /// the cabinet URL the user typed. + String get configuredApiBaseUrl => _config.apiBaseUrl; + bool get isDemo => _isDemo; + String? get lastError => _lastError; + http.Client? get httpClient => _httpClient; + + /// Changes whenever the backing data source changes, so feature controllers + /// can be rebuilt from scratch on login / logout. + String get sessionKey => '${_isDemo ? 'demo' : baseUrl}:${_user?.userId ?? 0}'; + + BudgetRepository get budgets => _repositories.budgets; + ExpenseRepository get expenses => _repositories.expenses; + JobRepository get jobs => _repositories.jobs; + UserRepository get users => _repositories.users; + + _Repositories get _repositories { + if (_isDemo || _token == null) return _demoRepositories; + return _apiRepositories ??= _buildApiRepositories(); + } + + _Repositories? _apiRepositories; + + late final _Repositories _demoRepositories = _Repositories( + budgets: _demo.budgets, + expenses: _demo.expenses, + jobs: _demo.jobs, + users: _demo.users, + ); + + _Repositories _buildApiRepositories() { + final client = ApiClient( + baseUrl: baseUrl, + tokenProvider: () => _token, + httpClient: _httpClient, + onUnauthorized: signOut, + ); + return _Repositories( + budgets: ApiBudgetRepository(client), + expenses: ApiExpenseRepository(client), + jobs: ApiJobRepository(client), + users: ApiUserRepository(client), + ); + } + + Future restore() async { + final stored = await _storage.readAll(); + final token = stored['token']; + final baseUrl = stored['baseUrl']; + + if (token != null && token.isNotEmpty && baseUrl != null && baseUrl.isNotEmpty) { + _token = token; + _baseUrl = baseUrl; + _isDemo = false; + _apiRepositories = null; + _user = AuthUser.tryDecode(stored['user']); + _status = SessionStatus.signedIn; + notifyListeners(); + return; + } + + if (_config.demoMode && _config.apiBaseUrl.isEmpty) { + _status = SessionStatus.signedOut; + notifyListeners(); + return; + } + + _status = SessionStatus.signedOut; + notifyListeners(); + } + + /// Signs in with a JWT issued by Telegram or Yandex (`POST /api/auth/*`). + Future signInWithToken({required String baseUrl, required String token}) async { + final normalizedUrl = baseUrl.trim().replaceAll(RegExp(r'/$'), ''); + final normalizedToken = token.trim(); + + if (normalizedUrl.isEmpty || normalizedToken.isEmpty) { + _lastError = 'Укажите адрес кабинета и токен'; + notifyListeners(); + return false; + } + + _lastError = null; + _baseUrl = normalizedUrl; + _token = normalizedToken; + _isDemo = false; + _apiRepositories = null; + + try { + final user = await _repositories.users.me(); + _user = user; + _status = SessionStatus.signedIn; + _config = _config.copyWith(apiBaseUrl: normalizedUrl, demoMode: false); + await _storage.write( + token: normalizedToken, + baseUrl: normalizedUrl, + user: user.encode(), + ); + notifyListeners(); + return true; + } on ApiException catch (error) { + _token = null; + _apiRepositories = null; + _lastError = error.message; + _status = SessionStatus.signedOut; + notifyListeners(); + return false; + } + } + + /// Runs the app against [DemoBackend] — no server required. + void startDemo() { + _isDemo = true; + _token = null; + _user = DemoBackend.user; + _lastError = null; + _status = SessionStatus.signedIn; + notifyListeners(); + } + + Future signOut() async { + await _storage.clear(); + _token = null; + _user = null; + _isDemo = false; + _apiRepositories = null; + _status = SessionStatus.signedOut; + notifyListeners(); + } +} + +class _Repositories { + const _Repositories({ + required this.budgets, + required this.expenses, + required this.jobs, + required this.users, + }); + + final BudgetRepository budgets; + final ExpenseRepository expenses; + final JobRepository jobs; + final UserRepository users; +} diff --git a/mobile/lib/features/auth/telegram_login.dart b/mobile/lib/features/auth/telegram_login.dart new file mode 100644 index 0000000..1bd937d --- /dev/null +++ b/mobile/lib/features/auth/telegram_login.dart @@ -0,0 +1,316 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:please_pay_me/data/models/json.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; + +/// Name of the JavaScript channel injected into the cabinet page. +/// Must stay in sync with `web/src/auth/telegramRedirect.ts`. +const telegramAuthChannel = 'PpmAuth'; + +/// Chrome-like UA without the `; wv` token Android WebView inserts. +/// Telegram's widget rejects the default WebView user-agent on some devices. +const telegramWebViewUserAgent = + 'Mozilla/5.0 (Linux; Android 13; Mobile) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36'; + +/// Runs inside the cabinet and hands the JWT back to Flutter. +/// +/// Two paths, both without backend changes: +/// 1. The web app posts to `PpmAuth` from `setSession` right after login. +/// 2. This script hooks `localStorage.setItem` and polls, so an already-open +/// session (or an older cabinet build) still works. +const telegramTokenProbeJs = ''' +(function () { + function ping() { + try { + var token = window.localStorage.getItem('ppm_session_jwt'); + if (token && window.$telegramAuthChannel) { + $telegramAuthChannel.postMessage(JSON.stringify({ token: token })); + } + } catch (error) {} + } + + if (!window.__ppmAuthHooked) { + window.__ppmAuthHooked = true; + try { + var original = window.localStorage.setItem.bind(window.localStorage); + window.localStorage.setItem = function (key, value) { + original(key, value); + if (key === 'ppm_session_jwt') ping(); + }; + } catch (error) {} + } + + ping(); +})(); +'''; + +/// WebView login only exists on mobile; desktop and web fall back to the token +/// form. +bool get isTelegramWebLoginSupported { + if (kIsWeb) return false; + return defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS; +} + +/// The cabinet and the API share an origin (nginx proxies `/api`), so the API +/// address can be derived unless it was configured explicitly. +String resolveApiBaseUrl({ + required String cabinetUrl, + String configuredApiBaseUrl = '', +}) { + if (configuredApiBaseUrl.trim().isNotEmpty) { + return configuredApiBaseUrl.trim().replaceAll(RegExp(r'/$'), ''); + } + + return resolveCabinetOrigin(cabinetUrl); +} + +/// Origin of the cabinet URL — used both as the API base and as a sanity check. +String resolveCabinetOrigin(String cabinetUrl) { + final uri = resolveCabinetLoginUri(cabinetUrl); + return uri.hasPort && !_isDefaultPort(uri) + ? '${uri.scheme}://${uri.host}:${uri.port}' + : '${uri.scheme}://${uri.host}'; +} + +/// Always open `/login` so the Telegram widget is on screen. +Uri resolveCabinetLoginUri(String cabinetUrl) { + var raw = cabinetUrl.trim(); + if (raw.isEmpty) return Uri.parse('https://localhost/login'); + + if (!raw.contains('://')) { + raw = 'https://$raw'; + } + + final uri = Uri.parse(raw); + final path = uri.path; + if (path.isEmpty || path == '/') { + return uri.replace(path: '/login'); + } + return uri; +} + +bool _isDefaultPort(Uri uri) { + return (uri.scheme == 'https' && uri.port == 443) || + (uri.scheme == 'http' && uri.port == 80); +} + +/// Extracts the JWT from the payload posted by [telegramTokenProbeJs]. +String? parseTelegramAuthMessage(String raw) { + try { + final token = asString(asMap(jsonDecode(raw))['token']); + return token.isEmpty ? null : token; + } on FormatException { + return null; + } +} + +bool isExternalAuthScheme(Uri uri) { + return uri.scheme == 'tg' || uri.scheme == 'telegram'; +} + +bool isTelegramOAuthHost(String host) { + return host == 'oauth.telegram.org' || + host == 'telegram.org' || + host.endsWith('.telegram.org'); +} + +/// Opens the cabinet in an in-app browser and resolves with the JWT once the +/// user has signed in through the Telegram Login Widget. +Future showTelegramLogin({ + required BuildContext context, + required String cabinetUrl, +}) { + return Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => TelegramLoginScreen(cabinetUrl: cabinetUrl), + ), + ); +} + +class TelegramLoginScreen extends StatefulWidget { + const TelegramLoginScreen({super.key, required this.cabinetUrl}); + + final String cabinetUrl; + + @override + State createState() => _TelegramLoginScreenState(); +} + +class _TelegramLoginScreenState extends State { + late final WebViewController _controller; + Timer? _poll; + bool _loading = true; + bool _completed = false; + bool _canGoBack = false; + String? _error; + + Uri get _startUri => resolveCabinetLoginUri(widget.cabinetUrl); + + @override + void initState() { + super.initState(); + + _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setUserAgent(telegramWebViewUserAgent) + ..setBackgroundColor(const Color(0x00000000)) + ..addJavaScriptChannel( + telegramAuthChannel, + onMessageReceived: (message) => _onToken(message.message), + ) + ..setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: _onNavigationRequest, + onPageStarted: (_) { + if (mounted) setState(() => _loading = true); + }, + onPageFinished: (_) { + _refreshCanGoBack(); + if (mounted) setState(() => _loading = false); + _probe(); + }, + onWebResourceError: (error) { + if (!mounted || _completed) return; + // Subframe errors (the Telegram iframe) must not kill the page. + if (error.isForMainFrame == false) return; + setState(() { + _loading = false; + _error = error.description; + }); + }, + ), + ); + + _configureAndroid(); + _controller.loadRequest(_startUri); + + // The widget writes the token after an async callback, so polling is more + // reliable than a single probe on page load. + _poll = Timer.periodic(const Duration(milliseconds: 700), (_) => _probe()); + } + + Future _configureAndroid() async { + if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) return; + + final platform = _controller.platform; + if (platform is! AndroidWebViewController) return; + + final cookies = AndroidWebViewCookieManager( + const PlatformWebViewCookieManagerCreationParams(), + ); + await cookies.setAcceptThirdPartyCookies(platform, true); + } + + @override + void dispose() { + _poll?.cancel(); + super.dispose(); + } + + Future _probe() async { + if (_completed) return; + try { + await _controller.runJavaScript(telegramTokenProbeJs); + } on PlatformException { + // The page may be mid-navigation; the next tick retries. + } + } + + Future _onNavigationRequest(NavigationRequest request) async { + final uri = Uri.tryParse(request.url); + if (uri == null) return NavigationDecision.navigate; + + if (isExternalAuthScheme(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + return NavigationDecision.prevent; + } + + return NavigationDecision.navigate; + } + + Future _refreshCanGoBack() async { + final canGoBack = await _controller.canGoBack(); + if (mounted && canGoBack != _canGoBack) { + setState(() => _canGoBack = canGoBack); + } + } + + void _onToken(String raw) { + if (_completed) return; + + final token = parseTelegramAuthMessage(raw); + if (token == null) return; + + _completed = true; + _poll?.cancel(); + Navigator.of(context).pop(token); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + navigationBar: AppNavBar( + title: 'Вход через Telegram', + subtitle: _startUri.host, + leading: CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () => Navigator.of(context).pop(), + child: const AppText.body('Закрыть', color: AppColors.accent), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (_canGoBack) + CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () async { + await _controller.goBack(); + await _refreshCanGoBack(); + }, + child: const AppIcon(CupertinoIcons.back, color: AppColors.accent), + ), + CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () { + setState(() => _error = null); + _controller.reload(); + }, + child: const AppIcon(CupertinoIcons.refresh, color: AppColors.accent), + ), + ], + ), + ), + child: SafeArea( + child: _error != null + ? AppErrorView( + message: _error!, + onRetry: () { + setState(() => _error = null); + _controller.reload(); + }, + ) + : Stack( + children: [ + WebViewWidget(controller: _controller), + if (_loading) const Center(child: AppSpinner()), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/features/auth/yandex_login.dart b/mobile/lib/features/auth/yandex_login.dart new file mode 100644 index 0000000..e8f7e77 --- /dev/null +++ b/mobile/lib/features/auth/yandex_login.dart @@ -0,0 +1,332 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; +import 'package:please_pay_me/data/api/api_client.dart'; +import 'package:please_pay_me/data/models/auth_user.dart'; +import 'package:please_pay_me/data/models/json.dart'; +import 'package:please_pay_me/features/auth/telegram_login.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; + +const yandexAuthorizeHost = 'oauth.yandex.ru'; + +/// Same platforms as Telegram WebView login. +bool get isYandexWebLoginSupported => isTelegramWebLoginSupported; + +class YandexAuthProvider { + const YandexAuthProvider({ + required this.enabled, + required this.clientId, + this.redirectUri, + }); + + final bool enabled; + final String clientId; + final String? redirectUri; + + bool get usable => enabled && clientId.isNotEmpty; + + factory YandexAuthProvider.fromJson(Map json) { + return YandexAuthProvider( + enabled: asBool(json['enabled']), + clientId: asString(json['client_id']), + redirectUri: asStringOrNull(json['redirect_uri']), + ); + } +} + +class AuthProviders { + const AuthProviders({this.yandex}); + + final YandexAuthProvider? yandex; + + factory AuthProviders.fromJson(Map json) { + final raw = json['yandex']; + if (raw is! Map) return const AuthProviders(); + return AuthProviders(yandex: YandexAuthProvider.fromJson(asMap(raw))); + } +} + +class YandexOAuthCallback { + const YandexOAuthCallback({this.code, this.error}); + + final String? code; + final String? error; +} + +Uri yandexAuthorizeUri({ + required String clientId, + required String redirectUri, +}) { + return Uri.https(yandexAuthorizeHost, '/authorize', { + 'response_type': 'code', + 'client_id': clientId, + 'redirect_uri': redirectUri, + 'force_confirm': 'yes', + }); +} + +/// Callback registered in the Yandex app: origin + trailing slash. +String cabinetYandexRedirectUri(String cabinetUrl, {String? configured}) { + if (configured != null && configured.isNotEmpty) { + final parsed = Uri.tryParse(configured); + if (parsed != null && parsed.host.toLowerCase() == Uri.parse(resolveCabinetOrigin(cabinetUrl)).host.toLowerCase()) { + return configured; + } + } + return '${resolveCabinetOrigin(cabinetUrl)}/'; +} + +/// @deprecated use [cabinetYandexRedirectUri] +String cabinetLoginRedirectUri(String cabinetUrl) => cabinetYandexRedirectUri(cabinetUrl); + +/// True when [uri] is the registered cabinet callback (code or error). +YandexOAuthCallback? parseYandexCallback(Uri uri, {required String redirectUri}) { + final expected = Uri.tryParse(redirectUri); + if (expected == null) return null; + if (uri.host.toLowerCase() != expected.host.toLowerCase()) return null; + + final expectedPath = expected.path.isEmpty ? '/' : expected.path; + if (_normalizePath(uri.path) != _normalizePath(expectedPath)) return null; + + final code = uri.queryParameters['code']?.trim(); + final error = uri.queryParameters['error_description']?.trim() ?? + uri.queryParameters['error']?.trim(); + + if ((code == null || code.isEmpty) && (error == null || error.isEmpty)) { + return null; + } + + return YandexOAuthCallback( + code: code == null || code.isEmpty ? null : code, + error: error == null || error.isEmpty ? null : error, + ); +} + +String _normalizePath(String path) { + if (path.isEmpty) return '/'; + return path.length > 1 && path.endsWith('/') ? path.substring(0, path.length - 1) : path; +} + +Future fetchAuthProviders( + String apiBaseUrl, { + http.Client? httpClient, +}) async { + final client = ApiClient( + baseUrl: apiBaseUrl, + tokenProvider: () => null, + httpClient: httpClient, + ); + return AuthProviders.fromJson(await client.getJson('/api/auth/providers')); +} + +Future exchangeYandexCode({ + required String apiBaseUrl, + required String code, + required String redirectUri, + http.Client? httpClient, +}) async { + final client = ApiClient( + baseUrl: apiBaseUrl, + tokenProvider: () => null, + httpClient: httpClient, + ); + final session = AuthSession.fromJson( + await client.postJson( + '/api/auth/yandex', + body: {'code': code, 'redirect_uri': redirectUri}, + ), + ); + return session.accessToken; +} + +/// Opens Yandex OAuth in a WebView, intercepts the cabinet callback, exchanges +/// the code on the API and returns a JWT. +Future showYandexLogin({ + required BuildContext context, + required String clientId, + required String redirectUri, + required Future Function(String code) exchangeCode, +}) { + return Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => YandexLoginScreen( + clientId: clientId, + redirectUri: redirectUri, + exchangeCode: exchangeCode, + ), + ), + ); +} + +class YandexLoginScreen extends StatefulWidget { + const YandexLoginScreen({ + super.key, + required this.clientId, + required this.redirectUri, + required this.exchangeCode, + }); + + final String clientId; + final String redirectUri; + final Future Function(String code) exchangeCode; + + @override + State createState() => _YandexLoginScreenState(); +} + +class _YandexLoginScreenState extends State { + late final WebViewController _controller; + bool _loading = true; + bool _completed = false; + String? _error; + + Uri get _startUri => yandexAuthorizeUri( + clientId: widget.clientId, + redirectUri: widget.redirectUri, + ); + + @override + void initState() { + super.initState(); + + _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setUserAgent(telegramWebViewUserAgent) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: _onNavigationRequest, + onPageStarted: (url) { + if (mounted) setState(() => _loading = true); + _tryFinish(url); + }, + onPageFinished: (_) { + if (mounted) setState(() => _loading = false); + }, + onWebResourceError: (error) { + if (!mounted || _completed) return; + if (error.isForMainFrame == false) return; + setState(() { + _loading = false; + _error = error.description; + }); + }, + ), + ); + + _configureAndroid(); + _controller.loadRequest(_startUri); + } + + Future _configureAndroid() async { + if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) return; + + final platform = _controller.platform; + if (platform is! AndroidWebViewController) return; + + final cookies = AndroidWebViewCookieManager( + const PlatformWebViewCookieManagerCreationParams(), + ); + await cookies.setAcceptThirdPartyCookies(platform, true); + } + + Future _onNavigationRequest(NavigationRequest request) async { + if (await _tryFinish(request.url)) { + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + } + + Future _tryFinish(String url) async { + if (_completed) return true; + + final uri = Uri.tryParse(url); + if (uri == null) return false; + + final callback = parseYandexCallback(uri, redirectUri: widget.redirectUri); + if (callback == null) return false; + + if (callback.error != null) { + _completed = true; + if (mounted) { + setState(() { + _loading = false; + _error = callback.error; + }); + } + return true; + } + + final code = callback.code; + if (code == null) return false; + + _completed = true; + if (mounted) setState(() => _loading = true); + + try { + final token = await widget.exchangeCode(code); + if (!mounted) return true; + Navigator.of(context).pop(token); + } on ApiException catch (error) { + if (!mounted) return true; + setState(() { + _loading = false; + _error = error.message; + _completed = false; + }); + } + return true; + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + navigationBar: AppNavBar( + title: 'Вход через Яндекс', + subtitle: yandexAuthorizeHost, + leading: CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () => Navigator.of(context).pop(), + child: const AppText.body('Закрыть', color: AppColors.accent), + ), + trailing: CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () { + setState(() { + _error = null; + _completed = false; + }); + _controller.loadRequest(_startUri); + }, + child: const AppIcon(CupertinoIcons.refresh, color: AppColors.accent), + ), + ), + child: SafeArea( + child: _error != null + ? AppErrorView( + message: _error!, + onRetry: () { + setState(() { + _error = null; + _completed = false; + }); + _controller.loadRequest(_startUri); + }, + ) + : Stack( + children: [ + WebViewWidget(controller: _controller), + if (_loading) const Center(child: AppSpinner()), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/features/budgets/budget_form_sheet.dart b/mobile/lib/features/budgets/budget_form_sheet.dart new file mode 100644 index 0000000..6601390 --- /dev/null +++ b/mobile/lib/features/budgets/budget_form_sheet.dart @@ -0,0 +1,256 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/format/formatters.dart'; +import 'package:please_pay_me/data/models/budget.dart'; +import 'package:please_pay_me/features/budgets/budgets_controller.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; + +/// Create / edit form for an envelope. +class BudgetFormSheet extends StatefulWidget { + const BudgetFormSheet({super.key, required this.onSubmit, this.initial}); + + final BudgetStatus? initial; + + final Future Function({ + required String name, + required double totalAmount, + required DateTime startDate, + required DateTime endDate, + required bool resetExpenses, + }) onSubmit; + + @override + State createState() => _BudgetFormSheetState(); +} + +class _BudgetFormSheetState extends State { + late final _nameController = TextEditingController( + text: widget.initial?.budget.name ?? '', + ); + late final _amountController = TextEditingController( + text: widget.initial == null + ? '' + : widget.initial!.budget.totalAmount.toStringAsFixed(0), + ); + + late DateTime _startDate = widget.initial?.budget.startDate ?? DateTime.now(); + late DateTime _endDate = + widget.initial?.budget.endDate ?? DateTime.now().add(const Duration(days: 14)); + + bool _resetExpenses = false; + bool _saving = false; + String? _error; + + bool get _isEditing => widget.initial != null; + + @override + void dispose() { + _nameController.dispose(); + _amountController.dispose(); + super.dispose(); + } + + Future _submit() async { + final name = _nameController.text.trim(); + final amount = double.tryParse( + _amountController.text.trim().replaceAll(',', '.').replaceAll(' ', ''), + ); + + if (name.isEmpty) { + setState(() => _error = 'Введите название бюджета'); + return; + } + if (amount == null || amount <= 0) { + setState(() => _error = 'Введите сумму больше нуля'); + return; + } + if (!_endDate.isAfter(_startDate)) { + setState(() => _error = 'Дата окончания должна быть позже начала'); + return; + } + + setState(() { + _saving = true; + _error = null; + }); + + final error = await widget.onSubmit( + name: name, + totalAmount: amount, + startDate: _startDate, + endDate: _endDate, + resetExpenses: _resetExpenses, + ); + + if (!mounted) return; + + if (error != null) { + setState(() { + _saving = false; + _error = error; + }); + return; + } + + Navigator.of(context).pop(true); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + navigationBar: AppNavBar( + title: _isEditing ? 'Бюджет' : 'Новый бюджет', + leading: CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: _saving ? null : () => Navigator.of(context).pop(false), + child: const AppText.body('Отмена', color: AppColors.accent), + ), + ), + child: SafeArea( + child: ListView( + padding: const EdgeInsets.only(top: AppSpacing.s4, bottom: AppSpacing.s6), + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AppTextField( + label: 'Название', + placeholder: 'До аванса', + controller: _nameController, + enabled: !_saving, + ), + const SizedBox(height: AppSpacing.s4), + AppTextField( + label: 'Сумма на период', + placeholder: '0', + controller: _amountController, + enabled: !_saving, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + ), + ], + ), + ), + const SizedBox(height: AppSpacing.s5), + AppListSection( + header: 'Период', + footer: 'Дневной лимит = остаток ÷ количество оставшихся дней.', + children: [ + AppListTile( + title: 'Начало', + value: formatShortDate(_startDate), + onTap: _saving ? null : () => _pickDate(isStart: true), + ), + AppListTile( + title: 'Окончание', + value: formatShortDate(_endDate), + onTap: _saving ? null : () => _pickDate(isStart: false), + ), + ], + ), + if (_isEditing) ...[ + const SizedBox(height: AppSpacing.s5), + AppListSection( + footer: 'Сбросить траты — обнулить потраченное по этому бюджету.', + children: [ + AppSwitchRow( + title: 'Сбросить траты', + value: _resetExpenses, + onChanged: _saving ? null : (v) => setState(() => _resetExpenses = v), + ), + ], + ), + ], + if (_error != null) + Padding( + padding: const EdgeInsets.fromLTRB( + AppSpacing.gutter, + AppSpacing.s3, + AppSpacing.gutter, + 0, + ), + child: AppText.footnote(_error!, color: AppColors.systemRed), + ), + const SizedBox(height: AppSpacing.s5), + Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: AppButton( + label: _isEditing ? 'Сохранить' : 'Создать бюджет', + loading: _saving, + onPressed: _submit, + ), + ), + ], + ), + ), + ); + } + + Future _pickDate({required bool isStart}) async { + final picked = await showAppDatePicker( + context: context, + initialDate: isStart ? _startDate : _endDate, + minimumDate: isStart ? null : _startDate, + ); + if (picked == null || !mounted) return; + + setState(() { + if (isStart) { + _startDate = picked; + if (!_endDate.isAfter(_startDate)) { + _endDate = _startDate.add(const Duration(days: 14)); + } + } else { + _endDate = picked; + } + }); + } +} + +/// Opens the sheet wired to [BudgetsController]. +Future showBudgetFormSheet({ + required BuildContext context, + required BudgetsController controller, + BudgetStatus? initial, +}) async { + final saved = await showAppFormSheet( + context: context, + builder: (_) => BudgetFormSheet( + initial: initial, + onSubmit: ({ + required name, + required totalAmount, + required startDate, + required endDate, + required resetExpenses, + }) { + if (initial == null) { + return controller.create( + name: name, + totalAmount: totalAmount, + endDate: endDate, + startDate: startDate, + ); + } + return controller.update( + budgetId: initial.budget.id, + name: name, + totalAmount: totalAmount, + endDate: endDate, + startDate: startDate, + resetExpenses: resetExpenses, + ); + }, + ), + ); + + if (saved == true && context.mounted) { + await showAppToast( + context, + message: initial == null ? 'Бюджет создан' : 'Бюджет обновлён', + ); + } +} diff --git a/mobile/lib/features/budgets/budgets_controller.dart b/mobile/lib/features/budgets/budgets_controller.dart new file mode 100644 index 0000000..9ffcd49 --- /dev/null +++ b/mobile/lib/features/budgets/budgets_controller.dart @@ -0,0 +1,137 @@ +import 'package:flutter/foundation.dart'; +import 'package:please_pay_me/core/state/async_value.dart'; +import 'package:please_pay_me/data/api/api_client.dart'; +import 'package:please_pay_me/data/models/budget.dart'; +import 'package:please_pay_me/data/repositories/repositories.dart'; + +/// Source of truth for budgets: the overview, the budget list and the expense +/// form all read the selected envelope from here. +class BudgetsController extends ChangeNotifier { + BudgetsController({ + required BudgetRepository budgets, + required ExpenseRepository expenses, + }) : _budgets = budgets, + _expenses = expenses; + + final BudgetRepository _budgets; + final ExpenseRepository _expenses; + + AsyncValue> _state = const AsyncValue.loading(); + bool _mutating = false; + + AsyncValue> get state => _state; + + /// True while a write is in flight — used to disable buttons. + bool get isMutating => _mutating; + + List get items => _state.valueOrNull ?? const []; + + /// Currently selected envelope; `null` when the user has no budgets at all. + BudgetStatus? get selected { + final all = items; + if (all.isEmpty) return null; + for (final status in all) { + if (status.selected) return status; + } + return all.first; + } + + bool get hasBudgets => items.isNotEmpty; + + Future load({bool silent = false}) async { + if (!silent) { + _state = const AsyncValue.loading(); + notifyListeners(); + } + + try { + _state = AsyncValue.data(await _budgets.list()); + } on ApiException catch (error) { + _state = AsyncValue.error(error.message); + } + notifyListeners(); + } + + Future select(int budgetId) { + return _mutate(() => _budgets.select(budgetId)); + } + + Future setActive(int budgetId, {required bool isActive}) { + return _mutate(() => _budgets.setActive(budgetId, isActive: isActive)); + } + + Future create({ + required String name, + required double totalAmount, + required DateTime endDate, + DateTime? startDate, + }) { + return _mutate( + () => _budgets.create( + name: name, + totalAmount: totalAmount, + endDate: endDate, + startDate: startDate, + ), + ); + } + + Future update({ + required int budgetId, + String? name, + double? totalAmount, + DateTime? endDate, + DateTime? startDate, + bool resetExpenses = false, + }) { + return _mutate( + () => _budgets.update( + budgetId: budgetId, + name: name, + totalAmount: totalAmount, + endDate: endDate, + startDate: startDate, + resetExpenses: resetExpenses, + ), + ); + } + + Future delete(int budgetId) => _mutate(() => _budgets.delete(budgetId)); + + Future addExpense({ + required double amount, + String? note, + DateTime? spentAt, + int? budgetId, + }) { + return _mutate( + () => _expenses.create( + amount: amount, + note: note, + spentAt: spentAt, + budgetId: budgetId ?? selected?.budget.id, + ), + ); + } + + Future undoLastExpense() { + return _mutate(() => _expenses.undoLast(budgetId: selected?.budget.id)); + } + + /// Runs a write, reloads the list and returns an error message or `null`. + Future _mutate(Future Function() action) async { + _mutating = true; + notifyListeners(); + + try { + await action(); + await load(silent: true); + return null; + } on ApiException catch (error) { + return error.message; + } finally { + _mutating = false; + notifyListeners(); + } + } +} diff --git a/mobile/lib/features/budgets/budgets_screen.dart b/mobile/lib/features/budgets/budgets_screen.dart new file mode 100644 index 0000000..68951a8 --- /dev/null +++ b/mobile/lib/features/budgets/budgets_screen.dart @@ -0,0 +1,201 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/format/formatters.dart'; +import 'package:please_pay_me/data/models/budget.dart'; +import 'package:please_pay_me/features/budgets/budget_form_sheet.dart'; +import 'package:please_pay_me/features/budgets/budgets_controller.dart'; +import 'package:please_pay_me/features/journal/journal_controller.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:provider/provider.dart'; + +/// All envelopes: pick the active one, edit, archive or delete. +class BudgetsScreen extends StatelessWidget { + const BudgetsScreen({super.key}); + + @override + Widget build(BuildContext context) { + final controller = context.watch(); + + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + child: CustomScrollView( + physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + AppLargeNavBar( + title: 'Бюджеты', + trailing: CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () => showBudgetFormSheet(context: context, controller: controller), + child: const AppIcon(CupertinoIcons.add_circled, color: AppColors.accent), + ), + ), + CupertinoSliverRefreshControl(onRefresh: () => controller.load(silent: true)), + SliverToBoxAdapter( + child: controller.state.map( + loading: () => AppListSection( + children: List.generate(3, (_) => const AppSkeletonRow()), + ), + error: (message) => AppErrorView(message: message, onRetry: controller.load), + data: (items) => items.isEmpty + ? AppEmptyState( + icon: CupertinoIcons.money_rubl_circle, + title: 'Бюджетов нет', + message: 'Создайте первый конверт до следующей зарплаты.', + actionLabel: 'Создать бюджет', + onAction: () => + showBudgetFormSheet(context: context, controller: controller), + ) + : _BudgetsList(items: items, controller: controller), + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s7)), + ], + ), + ); + } +} + +class _BudgetsList extends StatelessWidget { + const _BudgetsList({required this.items, required this.controller}); + + final List items; + final BudgetsController controller; + + @override + Widget build(BuildContext context) { + final active = items.where((status) => !status.isExpired).toList(); + final archived = items.where((status) => status.isExpired).toList(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (active.isNotEmpty) + AppListSection( + header: 'Активные', + footer: 'Нажмите, чтобы сделать бюджет текущим.', + separatorIndent: 60, + children: [ + for (final status in active) + _BudgetRow(status: status, controller: controller), + ], + ), + if (archived.isNotEmpty) ...[ + const SizedBox(height: AppSpacing.s5), + AppListSection( + header: 'Завершённые', + separatorIndent: 60, + children: [ + for (final status in archived) + _BudgetRow(status: status, controller: controller), + ], + ), + ], + ], + ); + } +} + +class _BudgetRow extends StatelessWidget { + const _BudgetRow({required this.status, required this.controller}); + + final BudgetStatus status; + final BudgetsController controller; + + @override + Widget build(BuildContext context) { + final currency = status.budget.currency; + final subtitle = status.isExpired + ? 'Завершён ${formatShortDate(status.budget.endDate)}' + : '${formatDaysLeft(status.daysLeft)} · лимит ${formatMoney(status.dailyLimit, currency: currency)}'; + + return AppListTile( + leading: AppIconBadge( + icon: status.selected ? CupertinoIcons.checkmark_alt : CupertinoIcons.tray_full, + color: status.selected + ? AppColors.accent + : status.isExpired + ? AppColors.systemGray + : AppColors.systemOrange, + ), + title: status.budget.name, + subtitle: subtitle, + value: formatMoney(status.remaining, currency: currency), + onTap: () => _openActions(context), + ); + } + + Future _openActions(BuildContext context) async { + final journal = context.read(); + final index = await showAppActionSheet( + context: context, + title: status.budget.name, + message: 'Остаток ${formatMoney(status.remaining, currency: status.budget.currency)}', + actions: [ + if (!status.selected) const AppActionSheetAction(label: 'Сделать текущим', isDefault: true), + const AppActionSheetAction(label: 'Редактировать'), + AppActionSheetAction(label: status.budget.isActive ? 'В архив' : 'Вернуть из архива'), + const AppActionSheetAction(label: 'Удалить', destructive: true), + ], + ); + + if (index == null || !context.mounted) return; + + final actions = [ + if (!status.selected) 'select', + 'edit', + 'archive', + 'delete', + ]; + + switch (actions[index]) { + case 'select': + final error = await controller.select(status.budget.id); + journal.bindBudget(controller.selected?.budget.id); + await journal.load(silent: true); + if (context.mounted) { + await showAppToast(context, message: error ?? 'Бюджет выбран'); + } + case 'edit': + await showBudgetFormSheet( + context: context, + controller: controller, + initial: status, + ); + case 'archive': + final error = await controller.setActive( + status.budget.id, + isActive: !status.budget.isActive, + ); + if (context.mounted) { + await showAppToast( + context, + message: error ?? (status.budget.isActive ? 'Бюджет в архиве' : 'Бюджет активен'), + ); + } + case 'delete': + await _confirmDelete(context, journal); + } + } + + Future _confirmDelete(BuildContext context, JournalController journal) async { + final confirmed = await showAppAlert( + context: context, + title: 'Удалить «${status.budget.name}»?', + message: 'Вместе с бюджетом удалятся все его операции.', + confirmLabel: 'Удалить', + cancelLabel: 'Отмена', + destructive: true, + ); + + if (confirmed != true) return; + + final error = await controller.delete(status.budget.id); + journal.bindBudget(controller.selected?.budget.id); + await journal.load(silent: true); + + if (context.mounted) { + await showAppToast(context, message: error ?? 'Бюджет удалён'); + } + } +} diff --git a/mobile/lib/features/expenses/expense_actions.dart b/mobile/lib/features/expenses/expense_actions.dart new file mode 100644 index 0000000..f842a6b --- /dev/null +++ b/mobile/lib/features/expenses/expense_actions.dart @@ -0,0 +1,73 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/format/formatters.dart'; +import 'package:please_pay_me/features/budgets/budgets_controller.dart'; +import 'package:please_pay_me/features/expenses/expense_form_sheet.dart'; +import 'package:please_pay_me/features/journal/journal_controller.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:provider/provider.dart'; + +/// Opens the expense form and keeps the journal in sync on success. +Future showExpenseFormSheet({ + required BuildContext context, + required BudgetsController controller, +}) async { + final selected = controller.selected; + final journal = context.read(); + + final saved = await showAppFormSheet( + context: context, + builder: (_) => ExpenseFormSheet( + budgetName: selected?.budget.name, + remainingToday: selected?.remainingToday, + onSubmit: ({required amount, note, required spentAt}) => controller.addExpense( + amount: amount, + note: note, + spentAt: spentAt, + ), + ), + ); + + if (saved != true) return; + + await journal.load(silent: true); + if (context.mounted) { + await showAppToast(context, message: 'Трата записана'); + } +} + +Future undoLastExpense({ + required BuildContext context, + required BudgetsController controller, +}) async { + final journal = context.read(); + final confirmed = await showAppAlert( + context: context, + title: 'Отменить последнюю трату?', + message: 'Операция будет удалена из текущего бюджета.', + confirmLabel: 'Отменить трату', + cancelLabel: 'Закрыть', + destructive: true, + ); + + if (confirmed != true) return; + + final error = await controller.undoLastExpense(); + await journal.load(silent: true); + + if (!context.mounted) return; + + await showAppToast( + context, + message: error ?? 'Последняя трата удалена', + icon: error == null + ? CupertinoIcons.arrow_uturn_left_circle_fill + : CupertinoIcons.exclamationmark_circle_fill, + ); +} + +/// Shared row renderer so the journal and the overview look identical. +String expenseTitle(String? note) => note?.trim().isNotEmpty == true ? note!.trim() : 'Без комментария'; + +String expenseAmount(double amount, {String currency = 'RUB'}) { + return formatSignedMoney(amount, currency: currency); +} diff --git a/mobile/lib/features/expenses/expense_form_sheet.dart b/mobile/lib/features/expenses/expense_form_sheet.dart new file mode 100644 index 0000000..6b6b4c2 --- /dev/null +++ b/mobile/lib/features/expenses/expense_form_sheet.dart @@ -0,0 +1,208 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/format/formatters.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; + +typedef ExpenseSubmit = Future Function({ + required double amount, + String? note, + required DateTime spentAt, +}); + +/// Modal form for a new expense. Submits through the caller so the sheet has +/// no knowledge of repositories. +class ExpenseFormSheet extends StatefulWidget { + const ExpenseFormSheet({ + super.key, + required this.onSubmit, + this.budgetName, + this.remainingToday, + }); + + final ExpenseSubmit onSubmit; + final String? budgetName; + final double? remainingToday; + + @override + State createState() => _ExpenseFormSheetState(); +} + +class _ExpenseFormSheetState extends State { + final _amountController = TextEditingController(); + final _noteController = TextEditingController(); + + DateTime _date = DateTime.now(); + bool _saving = false; + String? _error; + + static const _quickAmounts = [100.0, 250.0, 500.0, 1000.0]; + + @override + void dispose() { + _amountController.dispose(); + _noteController.dispose(); + super.dispose(); + } + + double? get _amount { + final raw = _amountController.text.trim().replaceAll(',', '.').replaceAll(' ', ''); + final value = double.tryParse(raw); + return value != null && value > 0 ? value : null; + } + + Future _submit() async { + final amount = _amount; + if (amount == null) { + setState(() => _error = 'Введите сумму больше нуля'); + return; + } + + setState(() { + _saving = true; + _error = null; + }); + + final note = _noteController.text.trim(); + final error = await widget.onSubmit( + amount: amount, + note: note.isEmpty ? null : note, + spentAt: _date, + ); + + if (!mounted) return; + + if (error != null) { + setState(() { + _saving = false; + _error = error; + }); + return; + } + + Navigator.of(context).pop(true); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + navigationBar: AppNavBar( + title: 'Новая трата', + subtitle: widget.budgetName, + leading: CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: _saving ? null : () => Navigator.of(context).pop(false), + child: const AppText.body('Отмена', color: AppColors.accent), + ), + ), + child: SafeArea( + child: ListView( + padding: const EdgeInsets.only(top: AppSpacing.s4, bottom: AppSpacing.s6), + children: [ + AppCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const AppText.footnote('Сумма'), + const SizedBox(height: AppSpacing.s1), + CupertinoTextField.borderless( + controller: _amountController, + autofocus: true, + placeholder: '0', + keyboardType: const TextInputType.numberWithOptions(decimal: true), + style: AppTypography.largeTitle.copyWith( + color: AppColors.of(context, AppColors.label), + ), + placeholderStyle: AppTypography.largeTitle.copyWith( + color: AppColors.of(context, AppColors.tertiaryLabel), + ), + padding: EdgeInsets.zero, + suffix: const AppText.title('₽', color: AppColors.secondaryLabel), + onChanged: (_) => setState(() => _error = null), + onSubmitted: (_) => _submit(), + ), + if (widget.remainingToday != null) ...[ + const SizedBox(height: AppSpacing.s2), + AppText.footnote( + 'На сегодня осталось ${formatMoney(widget.remainingToday!)}', + color: widget.remainingToday! < 0 + ? AppColors.systemRed + : AppColors.secondaryLabel, + ), + ], + ], + ), + ), + const SizedBox(height: AppSpacing.s3), + Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: Row( + children: [ + for (final amount in _quickAmounts) ...[ + AppChip( + label: formatMoney(amount, compact: true), + onPressed: () => setState(() { + _amountController.text = amount.toStringAsFixed(0); + _error = null; + }), + ), + const SizedBox(width: AppSpacing.s2), + ], + ], + ), + ), + const SizedBox(height: AppSpacing.s5), + AppListSection( + children: [ + AppListTile( + title: 'Дата', + value: formatRelativeDay(_date), + onTap: _saving ? null : _pickDate, + ), + ], + ), + const SizedBox(height: AppSpacing.s4), + Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: AppTextField( + controller: _noteController, + placeholder: 'Комментарий', + prefixIcon: CupertinoIcons.text_alignleft, + enabled: !_saving, + ), + ), + if (_error != null) + Padding( + padding: const EdgeInsets.fromLTRB( + AppSpacing.gutter, + AppSpacing.s3, + AppSpacing.gutter, + 0, + ), + child: AppText.footnote(_error!, color: AppColors.systemRed), + ), + const SizedBox(height: AppSpacing.s5), + Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: AppButton( + label: 'Записать трату', + loading: _saving, + onPressed: _submit, + ), + ), + ], + ), + ), + ); + } + + Future _pickDate() async { + final picked = await showAppDatePicker( + context: context, + initialDate: _date, + maximumDate: DateTime.now(), + ); + if (picked != null && mounted) setState(() => _date = picked); + } +} diff --git a/mobile/lib/features/journal/journal_controller.dart b/mobile/lib/features/journal/journal_controller.dart new file mode 100644 index 0000000..fd18997 --- /dev/null +++ b/mobile/lib/features/journal/journal_controller.dart @@ -0,0 +1,116 @@ +import 'package:flutter/foundation.dart'; +import 'package:please_pay_me/core/state/async_value.dart'; +import 'package:please_pay_me/data/api/api_client.dart'; +import 'package:please_pay_me/data/models/expense.dart'; +import 'package:please_pay_me/data/repositories/repositories.dart'; + +enum JournalScope { + current('Текущий'), + all('Все бюджеты'); + + const JournalScope(this.label); + + final String label; +} + +class ExpenseGroup { + const ExpenseGroup({required this.day, required this.items}); + + final DateTime day; + final List items; + + double get total => items.fold(0, (sum, expense) => sum + expense.amount); +} + +/// Paginated journal of operations with day grouping. +class JournalController extends ChangeNotifier { + JournalController({required ExpenseRepository expenses, this.pageSize = 20}) + : _expenses = expenses; + + final ExpenseRepository _expenses; + final int pageSize; + + AsyncValue _state = const AsyncValue.loading(); + JournalScope _scope = JournalScope.current; + int? _budgetId; + bool _loadingMore = false; + + AsyncValue get state => _state; + JournalScope get scope => _scope; + bool get isLoadingMore => _loadingMore; + + List get items => _state.valueOrNull?.items ?? const []; + + bool get hasMore => _state.valueOrNull?.hasMore ?? false; + + double get totalSum => _state.valueOrNull?.totalSum ?? 0; + + /// Operations bucketed by day, newest first — the journal renders one + /// inset-grouped section per bucket. + List get groups { + final buckets = >{}; + for (final expense in items) { + final day = DateTime(expense.spentAt.year, expense.spentAt.month, expense.spentAt.day); + buckets.putIfAbsent(day, () => []).add(expense); + } + + final days = buckets.keys.toList()..sort((a, b) => b.compareTo(a)); + return [for (final day in days) ExpenseGroup(day: day, items: buckets[day]!)]; + } + + void bindBudget(int? budgetId) { + if (_budgetId == budgetId) return; + _budgetId = budgetId; + load(silent: true); + } + + Future setScope(JournalScope scope) async { + if (_scope == scope) return; + _scope = scope; + notifyListeners(); + await load(); + } + + Future load({bool silent = false}) async { + if (!silent) { + _state = const AsyncValue.loading(); + notifyListeners(); + } + + try { + _state = AsyncValue.data(await _fetch(1)); + } on ApiException catch (error) { + _state = AsyncValue.error(error.message); + } + notifyListeners(); + } + + Future loadMore() async { + final current = _state.valueOrNull; + if (current == null || !current.hasMore || _loadingMore) return; + + _loadingMore = true; + notifyListeners(); + + try { + final next = await _fetch(current.page + 1); + _state = AsyncValue.data( + next.copyWithItems([...current.items, ...next.items]), + ); + } on ApiException catch (error) { + _state = AsyncValue.error(error.message); + } finally { + _loadingMore = false; + notifyListeners(); + } + } + + Future _fetch(int page) { + return _expenses.page( + page: page, + pageSize: pageSize, + budgetId: _scope == JournalScope.all ? null : _budgetId, + all: _scope == JournalScope.all, + ); + } +} diff --git a/mobile/lib/features/journal/journal_screen.dart b/mobile/lib/features/journal/journal_screen.dart new file mode 100644 index 0000000..49a2f9b --- /dev/null +++ b/mobile/lib/features/journal/journal_screen.dart @@ -0,0 +1,143 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/format/formatters.dart'; +import 'package:please_pay_me/features/budgets/budgets_controller.dart'; +import 'package:please_pay_me/features/expenses/expense_actions.dart'; +import 'package:please_pay_me/features/journal/journal_controller.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:provider/provider.dart'; + +/// Operations grouped by day, with current-budget / all-budgets scope. +class JournalScreen extends StatelessWidget { + const JournalScreen({super.key}); + + @override + Widget build(BuildContext context) { + final journal = context.watch(); + final budgets = context.watch(); + + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + child: CustomScrollView( + physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + AppLargeNavBar( + title: 'Журнал', + trailing: CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: budgets.hasBudgets + ? () => showExpenseFormSheet(context: context, controller: budgets) + : null, + child: const AppIcon(CupertinoIcons.add_circled, color: AppColors.accent), + ), + ), + CupertinoSliverRefreshControl(onRefresh: () => journal.load(silent: true)), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(top: AppSpacing.s2, bottom: AppSpacing.s4), + child: AppSegmentedControl( + labels: JournalScope.values.map((scope) => scope.label).toList(), + index: JournalScope.values.indexOf(journal.scope), + onChanged: (index) => journal.setScope(JournalScope.values[index]), + ), + ), + ), + SliverToBoxAdapter( + child: journal.state.map( + loading: () => AppListSection( + children: List.generate(4, (_) => const AppSkeletonRow(hasLeading: false)), + ), + error: (message) => AppErrorView(message: message, onRetry: journal.load), + data: (_) => _JournalBody(journal: journal), + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s7)), + ], + ), + ); + } +} + +class _JournalBody extends StatelessWidget { + const _JournalBody({required this.journal}); + + final JournalController journal; + + @override + Widget build(BuildContext context) { + final groups = journal.groups; + + if (groups.isEmpty) { + final budgets = context.read(); + return AppEmptyState( + icon: CupertinoIcons.doc_text, + title: 'Операций пока нет', + message: journal.scope == JournalScope.all + ? 'Как только появится первая трата, она появится здесь.' + : 'В текущем бюджете ещё ничего не потрачено.', + actionLabel: budgets.hasBudgets ? 'Добавить трату' : null, + onAction: budgets.hasBudgets + ? () => showExpenseFormSheet(context: context, controller: budgets) + : null, + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AppCard( + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const AppText.footnote('Всего операций'), + AppText.title('${journal.state.valueOrNull?.totalCount ?? 0}'), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const AppText.footnote('Сумма'), + AppText.title(formatMoney(journal.totalSum)), + ], + ), + ), + ], + ), + ), + const SizedBox(height: AppSpacing.s5), + for (final group in groups) ...[ + AppListSection( + header: formatRelativeDay(group.day), + footer: 'Итого за день: ${formatMoney(group.total)}', + children: [ + for (final expense in group.items) + AppListTile( + title: expenseTitle(expense.note), + subtitle: formatWeekday(expense.spentAt), + value: expenseAmount(expense.amount), + showChevron: false, + ), + ], + ), + const SizedBox(height: AppSpacing.s5), + ], + if (journal.hasMore) + Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: AppButton( + label: 'Показать ещё', + style: AppButtonStyle.gray, + loading: journal.isLoadingMore, + onPressed: journal.loadMore, + ), + ), + ], + ); + } +} diff --git a/mobile/lib/features/legal/legal_consent.dart b/mobile/lib/features/legal/legal_consent.dart new file mode 100644 index 0000000..6666b0f --- /dev/null +++ b/mobile/lib/features/legal/legal_consent.dart @@ -0,0 +1,143 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/legal/legal_links.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; + +class LegalAcceptance { + const LegalAcceptance({this.offer = false, this.consent = false}); + + final bool offer; + final bool consent; + + bool get accepted => offer && consent; + + LegalAcceptance copyWith({bool? offer, bool? consent}) { + return LegalAcceptance(offer: offer ?? this.offer, consent: consent ?? this.consent); + } +} + +class LegalConsentBlock extends StatelessWidget { + const LegalConsentBlock({ + super.key, + required this.value, + required this.onChanged, + required this.cabinetUrl, + }); + + final LegalAcceptance value; + final ValueChanged onChanged; + final String cabinetUrl; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + _LegalCheckRow( + checkboxKey: const Key('legal-offer-check'), + value: value.offer, + onChanged: (next) => onChanged(value.copyWith(offer: next)), + child: Text.rich( + TextSpan( + style: TextStyle( + fontSize: 13, + height: 1.35, + color: AppColors.of(context, AppColors.secondaryLabel), + ), + children: [ + const TextSpan(text: 'Я принимаю условия '), + _LinkSpan( + text: 'Пользовательского соглашения', + color: AppColors.of(context, AppColors.accent), + onTap: () => openLegalDocument(context, LegalLinks.resolve(cabinetUrl, LegalLinks.offer)), + ), + const TextSpan(text: ' (публичной оферты).'), + ], + ), + ), + ), + const SizedBox(height: AppSpacing.s3), + _LegalCheckRow( + checkboxKey: const Key('legal-consent-check'), + value: value.consent, + onChanged: (next) => onChanged(value.copyWith(consent: next)), + child: Text.rich( + TextSpan( + style: TextStyle( + fontSize: 13, + height: 1.35, + color: AppColors.of(context, AppColors.secondaryLabel), + ), + children: [ + const TextSpan( + text: + 'Я даю согласие на обработку моих персональных данных (email, аватар, данные о транзакциях), полученных от сервиса Яндекс и введённых мной, в целях предоставления доступа к Сервису. Согласие действует до его отзыва. ', + ), + _LinkSpan( + text: 'Текст согласия', + color: AppColors.of(context, AppColors.accent), + onTap: () => + openLegalDocument(context, LegalLinks.resolve(cabinetUrl, LegalLinks.consent)), + ), + ], + ), + ), + ), + ], + ); + } +} + +class _LinkSpan extends WidgetSpan { + _LinkSpan({required String text, required Color color, required VoidCallback onTap}) + : super( + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + child: GestureDetector( + onTap: onTap, + child: Text( + text, + style: TextStyle( + fontSize: 13, + height: 1.35, + color: color, + decoration: TextDecoration.underline, + ), + ), + ), + ); +} + +class _LegalCheckRow extends StatelessWidget { + const _LegalCheckRow({ + this.checkboxKey, + required this.value, + required this.onChanged, + required this.child, + }); + + final Key? checkboxKey; + final bool value; + final ValueChanged onChanged; + final Widget child; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => onChanged(!value), + behavior: HitTestBehavior.opaque, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + key: checkboxKey, + value ? CupertinoIcons.checkmark_square_fill : CupertinoIcons.square, + size: 22, + color: AppColors.of(context, value ? AppColors.accent : AppColors.systemGray), + ), + const SizedBox(width: AppSpacing.s2), + Expanded(child: child), + ], + ), + ); + } +} diff --git a/mobile/lib/features/overview/overview_screen.dart b/mobile/lib/features/overview/overview_screen.dart new file mode 100644 index 0000000..61e9ea5 --- /dev/null +++ b/mobile/lib/features/overview/overview_screen.dart @@ -0,0 +1,289 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/format/formatters.dart'; +import 'package:please_pay_me/data/models/budget.dart'; +import 'package:please_pay_me/data/models/job.dart'; +import 'package:please_pay_me/features/budgets/budget_form_sheet.dart'; +import 'package:please_pay_me/features/budgets/budgets_controller.dart'; +import 'package:please_pay_me/features/expenses/expense_actions.dart'; +import 'package:please_pay_me/features/journal/journal_controller.dart'; +import 'package:please_pay_me/features/work/jobs_controller.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:provider/provider.dart'; + +/// Home tab: current envelope, today's allowance and quick actions. +class OverviewScreen extends StatelessWidget { + const OverviewScreen({super.key}); + + @override + Widget build(BuildContext context) { + final budgets = context.watch(); + + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + child: CustomScrollView( + physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + const AppLargeNavBar(title: 'Обзор'), + CupertinoSliverRefreshControl( + onRefresh: () async { + await Future.wait([ + budgets.load(silent: true), + context.read().load(silent: true), + context.read().load(silent: true), + ]); + }, + ), + SliverToBoxAdapter( + child: budgets.state.map( + loading: () => const AppLoadingView(), + error: (message) => AppErrorView(message: message, onRetry: budgets.load), + data: (_) { + final selected = budgets.selected; + if (selected == null) return _NoBudgets(controller: budgets); + return _OverviewBody(status: selected); + }, + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s7)), + ], + ), + ); + } +} + +class _NoBudgets extends StatelessWidget { + const _NoBudgets({required this.controller}); + + final BudgetsController controller; + + @override + Widget build(BuildContext context) { + return AppEmptyState( + icon: CupertinoIcons.money_rubl_circle, + title: 'Бюджета пока нет', + message: 'Создайте конверт до следующей зарплаты — приложение посчитает дневной лимит.', + actionLabel: 'Создать бюджет', + onAction: () => showBudgetFormSheet(context: context, controller: controller), + ); + } +} + +class _OverviewBody extends StatelessWidget { + const _OverviewBody({required this.status}); + + final BudgetStatus status; + + @override + Widget build(BuildContext context) { + final budgets = context.watch(); + final currency = status.budget.currency; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AppCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded(child: AppText.headline(status.budget.name)), + AppChip( + label: status.isExpired + ? 'Завершён' + : formatDaysLeft(status.daysLeft).replaceFirst('осталось ', ''), + selected: !status.isExpired, + ), + ], + ), + const SizedBox(height: AppSpacing.s3), + const AppText.footnote('Остаток бюджета'), + AppText.largeTitle( + formatMoney(status.remaining, currency: currency), + color: status.isOverBudget ? AppColors.systemRed : AppColors.label, + ), + const SizedBox(height: AppSpacing.s4), + AppProgressBar( + value: status.spentProgress, + color: status.isOverBudget ? AppColors.systemRed : AppColors.accent, + ), + const SizedBox(height: AppSpacing.s2), + AppText.footnote( + 'Потрачено ${formatMoney(status.totalSpent, currency: currency)} ' + 'из ${formatMoney(status.budget.totalAmount, currency: currency)}', + ), + ], + ), + ), + const SizedBox(height: AppSpacing.s4), + _TodayCard(status: status), + const SizedBox(height: AppSpacing.s4), + Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: Row( + children: [ + Expanded( + child: AppButton( + label: 'Добавить трату', + icon: CupertinoIcons.plus, + onPressed: budgets.isMutating + ? null + : () => showExpenseFormSheet(context: context, controller: budgets), + ), + ), + const SizedBox(width: AppSpacing.s3), + AppButton( + label: 'Отменить', + style: AppButtonStyle.gray, + expanded: false, + onPressed: budgets.isMutating + ? null + : () => undoLastExpense(context: context, controller: budgets), + ), + ], + ), + ), + const SizedBox(height: AppSpacing.s5), + const _NextPaySection(), + const _RecentOperations(), + ], + ); + } +} + +class _TodayCard extends StatelessWidget { + const _TodayCard({required this.status}); + + final BudgetStatus status; + + @override + Widget build(BuildContext context) { + final currency = status.budget.currency; + final overspent = status.isOverDaily; + + return AppCard( + title: 'Сегодня', + subtitle: formatDay(status.today), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: _Metric( + label: 'Дневной лимит', + value: formatMoney(status.dailyLimit, currency: currency), + ), + ), + Expanded( + child: _Metric( + label: 'Потрачено', + value: formatMoney(status.spentToday, currency: currency), + color: overspent ? AppColors.systemRed : AppColors.label, + ), + ), + ], + ), + const SizedBox(height: AppSpacing.s4), + AppProgressBar( + value: status.dailyProgress, + color: overspent ? AppColors.systemRed : AppColors.systemGreen, + ), + const SizedBox(height: AppSpacing.s2), + AppText.footnote( + overspent + ? 'Лимит превышен на ${formatMoney(status.spentToday - status.dailyLimit, currency: currency)}' + : 'Можно потратить ещё ${formatMoney(status.remainingToday, currency: currency)}', + color: overspent ? AppColors.systemRed : AppColors.secondaryLabel, + ), + ], + ), + ); + } +} + +class _Metric extends StatelessWidget { + const _Metric({required this.label, required this.value, this.color = AppColors.label}); + + final String label; + final String value; + final Color color; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppText.footnote(label), + const SizedBox(height: 2), + AppText.title(value, color: color), + ], + ); + } +} + +class _NextPaySection extends StatelessWidget { + const _NextPaySection(); + + @override + Widget build(BuildContext context) { + final jobs = context.watch(); + final pay = jobs.nextPay; + if (pay == null) return const SizedBox.shrink(); + + return Padding( + padding: const EdgeInsets.only(bottom: AppSpacing.s5), + child: AppListSection( + header: 'Ближайшая выплата', + separatorIndent: 60, + children: [ + AppListTile( + leading: const AppIconBadge( + icon: CupertinoIcons.money_rubl_circle_fill, + color: AppColors.systemGreen, + ), + title: formatMoney(pay.amount), + subtitle: '${formatDay(pay.date)} · ${pay.percent.round()}% оклада', + value: _daysUntil(pay), + showChevron: false, + ), + ], + ), + ); + } + + String _daysUntil(UpcomingPay pay) { + final now = DateTime.now(); + final days = pay.date.difference(DateTime(now.year, now.month, now.day)).inDays; + return switch (days) { + <= 0 => 'сегодня', + 1 => 'завтра', + _ => 'через ${plural(days, 'день', 'дня', 'дней')}', + }; + } +} + +class _RecentOperations extends StatelessWidget { + const _RecentOperations(); + + @override + Widget build(BuildContext context) { + final journal = context.watch(); + final recent = journal.items.take(3).toList(); + if (recent.isEmpty) return const SizedBox.shrink(); + + return AppListSection( + header: 'Последние операции', + children: [ + for (final expense in recent) + AppListTile( + title: expense.note ?? 'Без комментария', + subtitle: formatRelativeDay(expense.spentAt), + value: formatSignedMoney(expense.amount), + showChevron: false, + ), + ], + ); + } +} diff --git a/mobile/lib/features/profile/profile_screen.dart b/mobile/lib/features/profile/profile_screen.dart new file mode 100644 index 0000000..7ef8db3 --- /dev/null +++ b/mobile/lib/features/profile/profile_screen.dart @@ -0,0 +1,228 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/branding/app_brand.dart'; +import 'package:please_pay_me/core/legal/legal_links.dart'; +import 'package:please_pay_me/features/auth/session_controller.dart'; +import 'package:please_pay_me/features/budgets/budgets_controller.dart'; +import 'package:please_pay_me/features/journal/journal_controller.dart'; +import 'package:please_pay_me/features/work/jobs_controller.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:provider/provider.dart'; + +class ProfileScreen extends StatelessWidget { + const ProfileScreen({super.key}); + + @override + Widget build(BuildContext context) { + final session = context.watch(); + final user = session.user; + + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + child: CustomScrollView( + physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + const AppLargeNavBar(title: 'Профиль'), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB( + AppSpacing.gutter, + AppSpacing.s2, + AppSpacing.gutter, + AppSpacing.s5, + ), + child: Row( + children: [ + AppAvatar( + initials: user?.initials, + imageUrl: user?.photoUrl, + radius: 32, + ), + const SizedBox(width: AppSpacing.s4), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppText.title(user?.displayName ?? 'Гость'), + AppText.subhead(user?.handle ?? 'не авторизован'), + ], + ), + ), + ], + ), + ), + ), + SliverToBoxAdapter( + child: AppListSection( + header: 'Подключение', + footer: session.isDemo + ? 'Демо-режим: данные живут только в памяти устройства.' + : 'Данные синхронизируются с веб-кабинетом «${AppBrand.name}».', + separatorIndent: 60, + children: [ + AppListTile( + leading: AppIconBadge( + icon: session.isDemo + ? CupertinoIcons.wrench_fill + : CupertinoIcons.cloud_fill, + color: session.isDemo ? AppColors.systemOrange : AppColors.systemGreen, + ), + title: 'Режим', + value: session.isDemo ? 'Демо' : 'Сервер', + showChevron: false, + ), + if (!session.isDemo) + AppListTile( + leading: const AppIconBadge( + icon: CupertinoIcons.link, + color: AppColors.systemGray, + ), + title: 'Адрес API', + subtitle: session.baseUrl, + showChevron: false, + ), + ], + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s5)), + SliverToBoxAdapter( + child: AppListSection( + header: 'Оформление', + footer: 'Системная повторяет тему устройства.', + children: [ + Padding( + padding: const EdgeInsets.fromLTRB( + AppSpacing.s3, + AppSpacing.s3, + AppSpacing.s3, + AppSpacing.s3, + ), + child: AppSegmentedControl( + padding: EdgeInsets.zero, + labels: const ['Системная', 'Светлая', 'Тёмная'], + index: context.watch().preference.index, + onChanged: (index) { + context.read().setPreference( + ThemePreference.values[index], + ); + }, + ), + ), + ], + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s5)), + SliverToBoxAdapter( + child: AppListSection( + separatorIndent: 60, + children: [ + AppListTile( + leading: const AppIconBadge( + icon: CupertinoIcons.arrow_clockwise, + color: AppColors.accent, + ), + title: 'Обновить данные', + onTap: () => _refreshAll(context), + ), + ], + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s5)), + SliverToBoxAdapter( + child: AppListSection( + header: 'Правовая информация', + footer: 'Открывается веб-версия на please-pay-me.ru.', + separatorIndent: 60, + children: [ + AppListTile( + leading: const AppIconBadge( + icon: CupertinoIcons.doc_text, + color: AppColors.accent, + ), + title: 'Пользовательское соглашение', + onTap: () => openLegalDocument( + context, + LegalLinks.resolve(session.webCabinetUrl, LegalLinks.offer), + ), + ), + AppListTile( + leading: const AppIconBadge( + icon: CupertinoIcons.lock_shield, + color: AppColors.systemGray, + ), + title: 'Политика конфиденциальности', + onTap: () => openLegalDocument( + context, + LegalLinks.resolve(session.webCabinetUrl, LegalLinks.privacy), + ), + ), + AppListTile( + leading: const AppIconBadge( + icon: CupertinoIcons.checkmark_shield, + color: AppColors.systemGray, + ), + title: 'Согласие на обработку данных', + onTap: () => openLegalDocument( + context, + LegalLinks.resolve(session.webCabinetUrl, LegalLinks.consent), + ), + ), + AppListTile( + leading: const AppIconBadge( + icon: CupertinoIcons.circle_grid_hex, + color: AppColors.systemGray, + ), + title: 'Политика cookie', + onTap: () => openLegalDocument( + context, + LegalLinks.resolve(session.webCabinetUrl, LegalLinks.cookies), + ), + ), + ], + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s5)), + SliverToBoxAdapter( + child: AppListSection( + footer: '${AppBrand.name} · версия 0.1.0', + children: [ + AppListTile( + title: 'Выйти', + destructive: true, + showChevron: false, + onTap: () => _signOut(context, session), + ), + ], + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s7)), + ], + ), + ); + } + + Future _refreshAll(BuildContext context) async { + await Future.wait([ + context.read().load(silent: true), + context.read().load(silent: true), + context.read().load(silent: true), + ]); + + if (context.mounted) { + await showAppToast(context, message: 'Данные обновлены'); + } + } + + Future _signOut(BuildContext context, SessionController session) async { + final confirmed = await showAppAlert( + context: context, + title: 'Выйти из аккаунта?', + message: 'Токен будет удалён с устройства.', + confirmLabel: 'Выйти', + cancelLabel: 'Отмена', + destructive: true, + ); + + if (confirmed == true) await session.signOut(); + } +} diff --git a/mobile/lib/features/splash/splash_screen.dart b/mobile/lib/features/splash/splash_screen.dart new file mode 100644 index 0000000..213b01f --- /dev/null +++ b/mobile/lib/features/splash/splash_screen.dart @@ -0,0 +1,129 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/branding/app_brand.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; + +/// Branded launch screen. Native Android/iOS splash uses the same background +/// and mark so the first Flutter frame does not flash. +class SplashScreen extends StatefulWidget { + const SplashScreen({super.key}); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Animation _mark; + late final Animation _copy; + late final Animation _spinner; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 720), + )..forward(); + _mark = CurvedAnimation( + parent: _controller, + curve: const Interval(0, 0.55, curve: Curves.easeOutCubic), + ); + _copy = CurvedAnimation( + parent: _controller, + curve: const Interval(0.28, 0.85, curve: Curves.easeOut), + ); + _spinner = CurvedAnimation( + parent: _controller, + curve: const Interval(0.55, 1, curve: Curves.easeOut), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final dark = CupertinoTheme.of(context).brightness == Brightness.dark; + + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + child: AnimatedBuilder( + animation: _controller, + builder: (context, _) { + return Stack( + fit: StackFit.expand, + children: [ + _Wash(dark: dark), + SafeArea( + child: Column( + children: [ + const Spacer(flex: 3), + Opacity( + opacity: _mark.value, + child: Transform.scale( + scale: 0.86 + (0.14 * _mark.value), + child: const AppBrandMark(), + ), + ), + const SizedBox(height: AppSpacing.s5), + Opacity( + opacity: _copy.value, + child: Transform.translate( + offset: Offset(0, 10 * (1 - _copy.value)), + child: const Column( + children: [ + AppText.largeTitle(AppBrand.name, textAlign: TextAlign.center), + SizedBox(height: AppSpacing.s2), + AppText.subhead( + 'Бюджет от зарплаты до зарплаты', + color: AppColors.secondaryLabel, + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + const Spacer(flex: 4), + Opacity( + opacity: _spinner.value, + child: const Padding( + padding: EdgeInsets.only(bottom: AppSpacing.s7), + child: AppSpinner(), + ), + ), + ], + ), + ), + ], + ); + }, + ), + ); + } +} + +class _Wash extends StatelessWidget { + const _Wash({required this.dark}); + + final bool dark; + + @override + Widget build(BuildContext context) { + final accent = AppColors.of(context, AppColors.accent).withValues(alpha: dark ? 0.14 : 0.1); + return IgnorePointer( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: RadialGradient( + center: const Alignment(0, -0.18), + radius: 0.85, + colors: [accent, const Color(0x00000000)], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/features/work/job_form_sheet.dart b/mobile/lib/features/work/job_form_sheet.dart new file mode 100644 index 0000000..1ebda97 --- /dev/null +++ b/mobile/lib/features/work/job_form_sheet.dart @@ -0,0 +1,304 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/format/formatters.dart'; +import 'package:please_pay_me/data/models/job.dart'; +import 'package:please_pay_me/features/work/jobs_controller.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; + +/// Create / edit form for a job and its payday schedule. +class JobFormSheet extends StatefulWidget { + const JobFormSheet({super.key, required this.onSubmit, this.initial}); + + final Job? initial; + + final Future Function({ + required String name, + required double salaryAmount, + required List payDays, + required double firstPayPercent, + required WeekendPolicy weekendPolicy, + }) onSubmit; + + @override + State createState() => _JobFormSheetState(); +} + +class _JobFormSheetState extends State { + late final _nameController = TextEditingController(text: widget.initial?.name ?? ''); + late final _salaryController = TextEditingController( + text: widget.initial == null ? '' : widget.initial!.salaryAmount.toStringAsFixed(0), + ); + + late int _firstDay = widget.initial?.payDays.firstOrNull ?? 5; + late int? _secondDay = + (widget.initial?.payDays.length ?? 0) > 1 ? widget.initial!.payDays[1] : 20; + late double _firstPercent = widget.initial?.firstPayPercent ?? 40; + late WeekendPolicy _policy = widget.initial?.weekendPolicy ?? WeekendPolicy.beforeWeekend; + + bool _saving = false; + String? _error; + + bool get _isEditing => widget.initial != null; + + @override + void dispose() { + _nameController.dispose(); + _salaryController.dispose(); + super.dispose(); + } + + Future _submit() async { + final name = _nameController.text.trim(); + final salary = double.tryParse( + _salaryController.text.trim().replaceAll(',', '.').replaceAll(' ', ''), + ); + + if (name.isEmpty) { + setState(() => _error = 'Введите название работы'); + return; + } + if (salary == null || salary <= 0) { + setState(() => _error = 'Введите оклад больше нуля'); + return; + } + + setState(() { + _saving = true; + _error = null; + }); + + final error = await widget.onSubmit( + name: name, + salaryAmount: salary, + payDays: [_firstDay, if (_secondDay != null) _secondDay!], + firstPayPercent: _secondDay == null ? 100 : _firstPercent, + weekendPolicy: _policy, + ); + + if (!mounted) return; + + if (error != null) { + setState(() { + _saving = false; + _error = error; + }); + return; + } + + Navigator.of(context).pop(true); + } + + @override + Widget build(BuildContext context) { + final salary = double.tryParse( + _salaryController.text.trim().replaceAll(',', '.').replaceAll(' ', ''), + ) ?? + 0; + + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + navigationBar: AppNavBar( + title: _isEditing ? 'Работа' : 'Новая работа', + leading: CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: _saving ? null : () => Navigator.of(context).pop(false), + child: const AppText.body('Отмена', color: AppColors.accent), + ), + ), + child: SafeArea( + child: ListView( + padding: const EdgeInsets.only(top: AppSpacing.s4, bottom: AppSpacing.s6), + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AppTextField( + label: 'Название', + placeholder: 'Основная работа', + controller: _nameController, + enabled: !_saving, + ), + const SizedBox(height: AppSpacing.s4), + AppTextField( + label: 'Оклад в месяц', + placeholder: '0', + controller: _salaryController, + enabled: !_saving, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + onChanged: (_) => setState(() {}), + ), + ], + ), + ), + const SizedBox(height: AppSpacing.s5), + AppListSection( + header: 'Дни выплат', + footer: 'Если день выпадает на выходные, выплата сдвигается по правилу ниже.', + children: [ + AppListTile( + title: 'Первая выплата', + value: '$_firstDay числа', + onTap: _saving ? null : () => _pickDay(isFirst: true), + ), + AppSwitchRow( + title: 'Вторая выплата', + value: _secondDay != null, + onChanged: _saving + ? null + : (value) => setState(() => _secondDay = value ? 20 : null), + ), + if (_secondDay != null) + AppListTile( + title: 'Вторая выплата', + value: '$_secondDay числа', + onTap: _saving ? null : () => _pickDay(isFirst: false), + ), + ], + ), + if (_secondDay != null) ...[ + const SizedBox(height: AppSpacing.s5), + AppCard( + title: 'Доля первой выплаты', + subtitle: '${_firstPercent.round()}% — ' + '${formatMoney(salary * _firstPercent / 100)} из ${formatMoney(salary)}', + child: CupertinoSlider( + value: _firstPercent, + min: 5, + max: 95, + divisions: 18, + activeColor: AppColors.of(context, AppColors.accent), + onChanged: _saving ? null : (v) => setState(() => _firstPercent = v), + ), + ), + ], + const SizedBox(height: AppSpacing.s5), + const AppSectionHeader('Если выплата на выходных'), + AppSegmentedControl( + labels: WeekendPolicy.values.map((policy) => policy.label).toList(), + index: WeekendPolicy.values.indexOf(_policy), + onChanged: (index) => setState(() => _policy = WeekendPolicy.values[index]), + ), + if (_error != null) + Padding( + padding: const EdgeInsets.fromLTRB( + AppSpacing.gutter, + AppSpacing.s3, + AppSpacing.gutter, + 0, + ), + child: AppText.footnote(_error!, color: AppColors.systemRed), + ), + const SizedBox(height: AppSpacing.s5), + Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + child: AppButton( + label: _isEditing ? 'Сохранить' : 'Добавить работу', + loading: _saving, + onPressed: _submit, + ), + ), + ], + ), + ), + ); + } + + Future _pickDay({required bool isFirst}) async { + final initial = isFirst ? _firstDay : (_secondDay ?? 20); + var picked = initial; + + final result = await showCupertinoModalPopup( + context: context, + builder: (ctx) => Container( + height: 280, + color: AppColors.of(ctx, AppColors.groupedSurface), + child: SafeArea( + top: false, + child: Column( + children: [ + Expanded( + child: CupertinoPicker( + itemExtent: 36, + scrollController: FixedExtentScrollController(initialItem: initial - 1), + onSelectedItemChanged: (index) => picked = index + 1, + children: [ + for (var day = 1; day <= 31; day++) Center(child: AppText.body('$day числа')), + ], + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB( + AppSpacing.gutter, + AppSpacing.s2, + AppSpacing.gutter, + AppSpacing.s3, + ), + child: AppButton( + label: 'Готово', + onPressed: () => Navigator.of(ctx).pop(picked), + ), + ), + ], + ), + ), + ), + ); + + if (result == null || !mounted) return; + setState(() { + if (isFirst) { + _firstDay = result; + } else { + _secondDay = result; + } + }); + } +} + +Future showJobFormSheet({ + required BuildContext context, + required JobsController controller, + Job? initial, +}) async { + final saved = await showAppFormSheet( + context: context, + builder: (_) => JobFormSheet( + initial: initial, + onSubmit: ({ + required name, + required salaryAmount, + required payDays, + required firstPayPercent, + required weekendPolicy, + }) { + if (initial == null) { + return controller.create( + name: name, + salaryAmount: salaryAmount, + payDays: payDays, + firstPayPercent: firstPayPercent, + weekendPolicy: weekendPolicy, + ); + } + return controller.update( + jobId: initial.id, + name: name, + salaryAmount: salaryAmount, + payDays: payDays, + firstPayPercent: firstPayPercent, + weekendPolicy: weekendPolicy, + ); + }, + ), + ); + + if (saved == true && context.mounted) { + await showAppToast( + context, + message: initial == null ? 'Работа добавлена' : 'Работа обновлена', + ); + } +} diff --git a/mobile/lib/features/work/jobs_controller.dart b/mobile/lib/features/work/jobs_controller.dart new file mode 100644 index 0000000..322ea92 --- /dev/null +++ b/mobile/lib/features/work/jobs_controller.dart @@ -0,0 +1,95 @@ +import 'package:flutter/foundation.dart'; +import 'package:please_pay_me/core/state/async_value.dart'; +import 'package:please_pay_me/data/api/api_client.dart'; +import 'package:please_pay_me/data/models/job.dart'; +import 'package:please_pay_me/data/repositories/repositories.dart'; + +class JobsController extends ChangeNotifier { + JobsController({required JobRepository jobs}) : _jobs = jobs; + + final JobRepository _jobs; + + AsyncValue> _state = const AsyncValue.loading(); + bool _mutating = false; + + AsyncValue> get state => _state; + bool get isMutating => _mutating; + List get items => _state.valueOrNull ?? const []; + + /// Nearest payday across all jobs — shown on the overview screen. + UpcomingPay? get nextPay { + final pays = items.expand((job) => job.nextPays).toList() + ..sort((a, b) => a.date.compareTo(b.date)); + return pays.isEmpty ? null : pays.first; + } + + Future load({bool silent = false}) async { + if (!silent) { + _state = const AsyncValue.loading(); + notifyListeners(); + } + + try { + _state = AsyncValue.data(await _jobs.list()); + } on ApiException catch (error) { + _state = AsyncValue.error(error.message); + } + notifyListeners(); + } + + Future create({ + required String name, + required double salaryAmount, + required List payDays, + required double firstPayPercent, + required WeekendPolicy weekendPolicy, + }) { + return _mutate( + () => _jobs.create( + name: name, + salaryAmount: salaryAmount, + payDays: payDays, + firstPayPercent: firstPayPercent, + weekendPolicy: weekendPolicy, + ), + ); + } + + Future update({ + required int jobId, + required String name, + required double salaryAmount, + required List payDays, + required double firstPayPercent, + required WeekendPolicy weekendPolicy, + }) { + return _mutate( + () => _jobs.update( + jobId: jobId, + name: name, + salaryAmount: salaryAmount, + payDays: payDays, + firstPayPercent: firstPayPercent, + weekendPolicy: weekendPolicy, + ), + ); + } + + Future delete(int jobId) => _mutate(() => _jobs.delete(jobId)); + + Future _mutate(Future Function() action) async { + _mutating = true; + notifyListeners(); + + try { + await action(); + await load(silent: true); + return null; + } on ApiException catch (error) { + return error.message; + } finally { + _mutating = false; + notifyListeners(); + } + } +} diff --git a/mobile/lib/features/work/work_screen.dart b/mobile/lib/features/work/work_screen.dart new file mode 100644 index 0000000..94e965d --- /dev/null +++ b/mobile/lib/features/work/work_screen.dart @@ -0,0 +1,166 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/format/formatters.dart'; +import 'package:please_pay_me/data/models/job.dart'; +import 'package:please_pay_me/features/work/job_form_sheet.dart'; +import 'package:please_pay_me/features/work/jobs_controller.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:provider/provider.dart'; + +/// Jobs and their payday schedule. +class WorkScreen extends StatelessWidget { + const WorkScreen({super.key}); + + @override + Widget build(BuildContext context) { + final controller = context.watch(); + + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + child: CustomScrollView( + physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + slivers: [ + AppLargeNavBar( + title: 'Работа', + trailing: CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () => showJobFormSheet(context: context, controller: controller), + child: const AppIcon(CupertinoIcons.add_circled, color: AppColors.accent), + ), + ), + CupertinoSliverRefreshControl(onRefresh: () => controller.load(silent: true)), + SliverToBoxAdapter( + child: controller.state.map( + loading: () => AppListSection( + children: List.generate(2, (_) => const AppSkeletonRow()), + ), + error: (message) => AppErrorView(message: message, onRetry: controller.load), + data: (jobs) => jobs.isEmpty + ? AppEmptyState( + icon: CupertinoIcons.briefcase, + title: 'Работа не добавлена', + message: 'Укажите оклад и дни выплат — приложение подскажет даты зарплаты.', + actionLabel: 'Добавить работу', + onAction: () => + showJobFormSheet(context: context, controller: controller), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (final job in jobs) ...[ + _JobCard(job: job, controller: controller), + const SizedBox(height: AppSpacing.s5), + ], + ], + ), + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: AppSpacing.s7)), + ], + ), + ); + } +} + +class _JobCard extends StatelessWidget { + const _JobCard({required this.job, required this.controller}); + + final Job job; + final JobsController controller; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AppListSection( + header: job.name, + separatorIndent: 60, + children: [ + AppListTile( + leading: const AppIconBadge( + icon: CupertinoIcons.briefcase_fill, + color: AppColors.accent, + ), + title: 'Оклад', + value: formatMoney(job.salaryAmount, currency: job.currency), + showChevron: false, + ), + AppListTile( + leading: const AppIconBadge( + icon: CupertinoIcons.calendar, + color: AppColors.systemOrange, + ), + title: 'Дни выплат', + value: job.payDays.map((day) => '$day').join(' и '), + showChevron: false, + ), + AppListTile( + leading: const AppIconBadge( + icon: CupertinoIcons.arrow_left_right, + color: AppColors.systemGray, + ), + title: 'Выходные', + value: job.weekendPolicy.label, + showChevron: false, + ), + AppListTile( + title: 'Настроить', + onTap: () => _openActions(context), + ), + ], + ), + if (job.nextPays.isNotEmpty) ...[ + const SizedBox(height: AppSpacing.s4), + AppListSection( + header: 'Ближайшие выплаты', + children: [ + for (final pay in job.nextPays) + AppListTile( + title: formatDay(pay.date), + subtitle: '${pay.percent.round()}% оклада · ${pay.scheduledDay} числа', + value: formatMoney(pay.amount, currency: job.currency), + showChevron: false, + ), + ], + ), + ], + ], + ); + } + + Future _openActions(BuildContext context) async { + final index = await showAppActionSheet( + context: context, + title: job.name, + actions: const [ + AppActionSheetAction(label: 'Редактировать', isDefault: true), + AppActionSheetAction(label: 'Удалить', destructive: true), + ], + ); + + if (index == null || !context.mounted) return; + + if (index == 0) { + await showJobFormSheet(context: context, controller: controller, initial: job); + return; + } + + final confirmed = await showAppAlert( + context: context, + title: 'Удалить «${job.name}»?', + message: 'График выплат тоже будет удалён.', + confirmLabel: 'Удалить', + cancelLabel: 'Отмена', + destructive: true, + ); + + if (confirmed != true) return; + + final error = await controller.delete(job.id); + if (context.mounted) { + await showAppToast(context, message: error ?? 'Работа удалена'); + } + } +} diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart new file mode 100644 index 0000000..03a3645 --- /dev/null +++ b/mobile/lib/main.dart @@ -0,0 +1,28 @@ +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:please_pay_me/app/app.dart'; +import 'package:please_pay_me/core/config/app_config.dart'; +import 'package:please_pay_me/core/config/env_loader.dart'; +import 'package:please_pay_me/core/storage/session_storage.dart'; +import 'package:please_pay_me/features/auth/session_controller.dart'; +import 'package:please_pay_me/theme/theme.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + await initializeDateFormatting('ru'); + + final session = SessionController( + config: AppConfig.fromEnvironment(file: await loadEnvFile()), + storage: const PrefsSessionStorage(), + ); + final theme = ThemeController(); + await theme.restore(); + + runApp(PleasePayMeApp(session: session, theme: theme)); + await Future.wait([ + session.restore(), + Future.delayed(const Duration(milliseconds: 850)), + ]); +} diff --git a/mobile/lib/theme/app_theme.dart b/mobile/lib/theme/app_theme.dart new file mode 100644 index 0000000..0e71171 --- /dev/null +++ b/mobile/lib/theme/app_theme.dart @@ -0,0 +1,53 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +/// Inter is the closest cross-platform stand-in for SF Pro, so the catalog +/// looks the same on Windows/web as it does on device. +TextStyle _sf(TextStyle style, {Color? color}) { + return GoogleFonts.inter(textStyle: style, color: color); +} + +CupertinoTextThemeData _textTheme() { + return CupertinoTextThemeData( + primaryColor: AppColors.accent, + textStyle: _sf(AppTypography.body, color: AppColors.label), + actionTextStyle: _sf(AppTypography.body, color: AppColors.accent), + tabLabelTextStyle: _sf(AppTypography.caption2, color: AppColors.secondaryLabel), + navTitleTextStyle: _sf(AppTypography.headline, color: AppColors.label), + navLargeTitleTextStyle: _sf(AppTypography.largeTitle, color: AppColors.label), + navActionTextStyle: _sf(AppTypography.body, color: AppColors.accent), + ); +} + +CupertinoThemeData buildLightTheme() => _buildTheme(Brightness.light); + +CupertinoThemeData buildDarkTheme() => _buildTheme(Brightness.dark); + +CupertinoThemeData _buildTheme(Brightness brightness) { + return CupertinoThemeData( + brightness: brightness, + primaryColor: AppColors.accent, + primaryContrastingColor: const Color(0xFFFFFFFF), + scaffoldBackgroundColor: AppColors.groupedBackground, + barBackgroundColor: AppColors.barBackground, + applyThemeToAll: true, + textTheme: _textTheme(), + ); +} + +/// Android system bars. Dark uses transparent black so the 3-button / gesture +/// bar does not get a gray contrast scrim over the tab bar. +SystemUiOverlayStyle systemUiOverlayFor(Brightness brightness) { + final dark = brightness == Brightness.dark; + return SystemUiOverlayStyle( + statusBarColor: const Color(0x00000000), + statusBarBrightness: brightness, + statusBarIconBrightness: dark ? Brightness.light : Brightness.dark, + systemNavigationBarColor: dark ? const Color(0x00000000) : const Color(0x00FFFFFF), + systemNavigationBarDividerColor: const Color(0x00000000), + systemNavigationBarIconBrightness: dark ? Brightness.light : Brightness.dark, + systemNavigationBarContrastEnforced: false, + ); +} diff --git a/mobile/lib/theme/theme.dart b/mobile/lib/theme/theme.dart new file mode 100644 index 0000000..ec7e9fc --- /dev/null +++ b/mobile/lib/theme/theme.dart @@ -0,0 +1,3 @@ +export 'app_theme.dart'; +export 'theme_controller.dart'; +export 'tokens.dart'; diff --git a/mobile/lib/theme/theme_controller.dart b/mobile/lib/theme/theme_controller.dart new file mode 100644 index 0000000..e51270a --- /dev/null +++ b/mobile/lib/theme/theme_controller.dart @@ -0,0 +1,81 @@ +import 'package:flutter/cupertino.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +enum ThemePreference { + system, + light, + dark; + + static ThemePreference parse(String? raw) { + return switch (raw) { + 'light' => ThemePreference.light, + 'dark' => ThemePreference.dark, + _ => ThemePreference.system, + }; + } + + Brightness resolve(Brightness platform) => switch (this) { + ThemePreference.system => platform, + ThemePreference.light => Brightness.light, + ThemePreference.dark => Brightness.dark, + }; +} + +abstract interface class ThemePreferenceStore { + Future read(); + Future write(String value); +} + +class PrefsThemeStore implements ThemePreferenceStore { + const PrefsThemeStore(); + + static const key = 'ppm_theme_preference'; + + @override + Future read() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString(key); + } + + @override + Future write(String value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(key, value); + } +} + +class MemoryThemeStore implements ThemePreferenceStore { + MemoryThemeStore([this.value]); + + String? value; + + @override + Future read() async => value; + + @override + Future write(String next) async => value = next; +} + +/// Survives logout: appearance is a device preference, not a session one. +class ThemeController extends ChangeNotifier { + ThemeController({ThemePreferenceStore? store}) : _store = store ?? const PrefsThemeStore(); + + final ThemePreferenceStore _store; + ThemePreference _preference = ThemePreference.system; + + ThemePreference get preference => _preference; + + Brightness resolve(Brightness platform) => _preference.resolve(platform); + + Future restore() async { + _preference = ThemePreference.parse(await _store.read()); + notifyListeners(); + } + + Future setPreference(ThemePreference value) async { + if (_preference == value) return; + _preference = value; + notifyListeners(); + await _store.write(value.name); + } +} diff --git a/mobile/lib/theme/tokens.dart b/mobile/lib/theme/tokens.dart new file mode 100644 index 0000000..e4b364e --- /dev/null +++ b/mobile/lib/theme/tokens.dart @@ -0,0 +1,138 @@ +/// Design tokens for the iOS-styled kit. +/// +/// Semantic colors follow Apple HIG (label / separator / grouped background) +/// and are declared as [CupertinoDynamicColor] so widgets resolve light & dark +/// automatically. Brand tint stays green to match the web cabinet. +library; + +import 'package:flutter/cupertino.dart'; + +abstract final class AppColors { + /// Brand tint — replaces `systemBlue` across the kit. + static const accent = CupertinoDynamicColor.withBrightness( + color: Color(0xFF12885A), + darkColor: Color(0xFF3CD68C), + ); + + static const accentSoft = CupertinoDynamicColor.withBrightness( + color: Color(0xFFE4F4EC), + darkColor: Color(0xFF14301F), + ); + + // —— iOS system palette —— + static const systemRed = CupertinoDynamicColor.withBrightness( + color: Color(0xFFFF3B30), + darkColor: Color(0xFFFF453A), + ); + static const systemOrange = CupertinoDynamicColor.withBrightness( + color: Color(0xFFFF9500), + darkColor: Color(0xFFFF9F0A), + ); + static const systemGreen = CupertinoDynamicColor.withBrightness( + color: Color(0xFF34C759), + darkColor: Color(0xFF30D158), + ); + static const systemGray = CupertinoDynamicColor.withBrightness( + color: Color(0xFF8E8E93), + darkColor: Color(0xFF8E8E93), + ); + static const systemGray3 = CupertinoDynamicColor.withBrightness( + color: Color(0xFFC7C7CC), + darkColor: Color(0xFF48484A), + ); + static const systemGray5 = CupertinoDynamicColor.withBrightness( + color: Color(0xFFE5E5EA), + darkColor: Color(0xFF2C2C2E), + ); + static const systemGray6 = CupertinoDynamicColor.withBrightness( + color: Color(0xFFF2F2F7), + darkColor: Color(0xFF1C1C1E), + ); + + // —— Text hierarchy —— + static const label = CupertinoDynamicColor.withBrightness( + color: Color(0xFF000000), + darkColor: Color(0xFFFFFFFF), + ); + static const secondaryLabel = CupertinoDynamicColor.withBrightness( + color: Color(0x993C3C43), + darkColor: Color(0x99EBEBF5), + ); + static const tertiaryLabel = CupertinoDynamicColor.withBrightness( + color: Color(0x4D3C3C43), + darkColor: Color(0x4DEBEBF5), + ); + + // —— Separators & surfaces —— + static const separator = CupertinoDynamicColor.withBrightness( + color: Color(0x4A3C3C43), + darkColor: Color(0xA6545458), + ); + static const opaqueSeparator = CupertinoDynamicColor.withBrightness( + color: Color(0xFFC6C6C8), + darkColor: Color(0xFF38383A), + ); + static const groupedBackground = CupertinoDynamicColor.withBrightness( + color: Color(0xFFF2F2F7), + darkColor: Color(0xFF000000), + ); + static const groupedSurface = CupertinoDynamicColor.withBrightness( + color: Color(0xFFFFFFFF), + darkColor: Color(0xFF1C1C1E), + ); + static const barBackground = CupertinoDynamicColor.withBrightness( + color: Color(0xF0F9F9F9), + darkColor: Color(0xF01D1D1D), + ); + + static Color of(BuildContext context, Color color) => + CupertinoDynamicColor.resolve(color, context); +} + +/// 4pt grid; iOS content inset is 16. +abstract final class AppSpacing { + static const double s1 = 4; + static const double s2 = 8; + static const double s3 = 12; + static const double s4 = 16; + static const double s5 = 20; + static const double s6 = 28; + static const double s7 = 40; + static const double s8 = 56; + + /// Standard leading/trailing inset for grouped content. + static const double gutter = 16; +} + +abstract final class AppRadii { + static const double sm = 6; + static const double md = 10; + + /// Inset-grouped cards & large buttons. + static const double lg = 12; + static const double xl = 16; + static const double capsule = 999; +} + +abstract final class AppSizes { + static const double buttonLarge = 50; + static const double buttonMedium = 44; + static const double buttonSmall = 34; + static const double rowMinHeight = 44; + static const double hairline = 0.5; +} + +/// SF Pro text scale (Apple HIG). +abstract final class AppTypography { + static const largeTitle = TextStyle(fontSize: 34, height: 1.2, fontWeight: FontWeight.w700, letterSpacing: 0.37); + static const title1 = TextStyle(fontSize: 28, height: 1.2, fontWeight: FontWeight.w700, letterSpacing: 0.36); + static const title2 = TextStyle(fontSize: 22, height: 1.25, fontWeight: FontWeight.w700, letterSpacing: 0.35); + static const title3 = TextStyle(fontSize: 20, height: 1.25, fontWeight: FontWeight.w600, letterSpacing: 0.38); + static const headline = TextStyle(fontSize: 17, height: 1.3, fontWeight: FontWeight.w600, letterSpacing: -0.41); + static const body = TextStyle(fontSize: 17, height: 1.3, fontWeight: FontWeight.w400, letterSpacing: -0.41); + static const callout = TextStyle(fontSize: 16, height: 1.3, fontWeight: FontWeight.w400, letterSpacing: -0.32); + static const subhead = TextStyle(fontSize: 15, height: 1.3, fontWeight: FontWeight.w400, letterSpacing: -0.24); + static const footnote = TextStyle(fontSize: 13, height: 1.3, fontWeight: FontWeight.w400, letterSpacing: -0.08); + static const caption1 = TextStyle(fontSize: 12, height: 1.3, fontWeight: FontWeight.w400); + static const caption2 = TextStyle(fontSize: 11, height: 1.3, fontWeight: FontWeight.w400, letterSpacing: 0.07); +} diff --git a/mobile/lib/ui/atoms/app_brand_mark.dart b/mobile/lib/ui/atoms/app_brand_mark.dart new file mode 100644 index 0000000..7406750 --- /dev/null +++ b/mobile/lib/ui/atoms/app_brand_mark.dart @@ -0,0 +1,35 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +/// App glyph: rounded green tile with a ruble, same language as [AppIconBadge]. +class AppBrandMark extends StatelessWidget { + const AppBrandMark({super.key, this.size = 96}); + + final double size; + + @override + Widget build(BuildContext context) { + final radius = size * 0.235; + return Container( + width: size, + height: size, + decoration: BoxDecoration( + color: AppColors.of(context, AppColors.accent), + borderRadius: BorderRadius.circular(radius), + boxShadow: [ + BoxShadow( + color: AppColors.of(context, AppColors.accent).withValues(alpha: 0.28), + blurRadius: size * 0.28, + offset: Offset(0, size * 0.08), + ), + ], + ), + alignment: Alignment.center, + child: Icon( + CupertinoIcons.money_rubl, + size: size * 0.52, + color: const Color(0xFFFFFFFF), + ), + ); + } +} diff --git a/mobile/lib/ui/atoms/app_button.dart b/mobile/lib/ui/atoms/app_button.dart new file mode 100644 index 0000000..85f0111 --- /dev/null +++ b/mobile/lib/ui/atoms/app_button.dart @@ -0,0 +1,105 @@ +import 'package:flutter/cupertino.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +/// iOS 15+ button styles. +enum AppButtonStyle { filled, tinted, gray, plain, destructive } + +/// Apple control sizes: large 50pt, medium 44pt, small 34pt. +enum AppButtonSize { large, medium, small } + +class AppButton extends StatelessWidget { + const AppButton({ + super.key, + required this.label, + this.onPressed, + this.style = AppButtonStyle.filled, + this.size = AppButtonSize.large, + this.expanded = true, + this.icon, + this.loading = false, + }); + + final String label; + final VoidCallback? onPressed; + final AppButtonStyle style; + final AppButtonSize size; + final bool expanded; + final IconData? icon; + final bool loading; + + double get _height => switch (size) { + AppButtonSize.large => AppSizes.buttonLarge, + AppButtonSize.medium => AppSizes.buttonMedium, + AppButtonSize.small => AppSizes.buttonSmall, + }; + + TextStyle get _textStyle => switch (size) { + AppButtonSize.large => AppTypography.headline, + AppButtonSize.medium => AppTypography.body, + AppButtonSize.small => AppTypography.subhead.copyWith(fontWeight: FontWeight.w600), + }; + + @override + Widget build(BuildContext context) { + final accent = AppColors.of(context, AppColors.accent); + final red = AppColors.of(context, AppColors.systemRed); + + final (Color background, Color foreground) = switch (style) { + AppButtonStyle.filled => (accent, const Color(0xFFFFFFFF)), + AppButtonStyle.tinted => (accent.withValues(alpha: 0.15), accent), + AppButtonStyle.gray => ( + AppColors.of(context, AppColors.systemGray5), + AppColors.of(context, AppColors.label), + ), + AppButtonStyle.plain => (const Color(0x00000000), accent), + AppButtonStyle.destructive => (red.withValues(alpha: 0.15), red), + }; + + final disabled = onPressed == null || loading; + final content = loading + ? CupertinoActivityIndicator( + radius: size == AppButtonSize.small ? 8 : 10, + color: foreground, + ) + : Row( + mainAxisSize: expanded ? MainAxisSize.max : MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (icon != null) ...[ + Icon(icon, size: size == AppButtonSize.small ? 16 : 19, color: foreground), + const SizedBox(width: AppSpacing.s2), + ], + Flexible( + child: Text( + label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.inter(textStyle: _textStyle, color: foreground), + ), + ), + ], + ); + + return Opacity( + opacity: disabled && !loading ? 0.35 : 1, + child: SizedBox( + height: _height, + width: expanded ? double.infinity : null, + child: CupertinoButton( + onPressed: disabled ? null : onPressed, + color: style == AppButtonStyle.plain ? null : background, + disabledColor: background, + borderRadius: BorderRadius.circular( + size == AppButtonSize.small ? AppRadii.md : AppRadii.lg, + ), + padding: EdgeInsets.symmetric( + horizontal: size == AppButtonSize.small ? AppSpacing.s3 : AppSpacing.s4, + ), + minimumSize: Size.zero, + child: content, + ), + ), + ); + } +} diff --git a/mobile/lib/ui/atoms/app_icon.dart b/mobile/lib/ui/atoms/app_icon.dart new file mode 100644 index 0000000..bf4a628 --- /dev/null +++ b/mobile/lib/ui/atoms/app_icon.dart @@ -0,0 +1,55 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +class AppIcon extends StatelessWidget { + const AppIcon( + this.icon, { + super.key, + this.size = 22, + this.color = AppColors.label, + this.semanticLabel, + }); + + final IconData icon; + final double size; + final Color color; + final String? semanticLabel; + + @override + Widget build(BuildContext context) { + return Icon( + icon, + size: size, + color: AppColors.of(context, color), + semanticLabel: semanticLabel, + ); + } +} + +/// Settings-style rounded square glyph used as a list row leading item. +class AppIconBadge extends StatelessWidget { + const AppIconBadge({ + super.key, + required this.icon, + this.size = 29, + this.color = AppColors.accent, + }); + + final IconData icon; + final double size; + final Color color; + + @override + Widget build(BuildContext context) { + return Container( + width: size, + height: size, + decoration: BoxDecoration( + color: AppColors.of(context, color), + borderRadius: BorderRadius.circular(size * 0.235), + ), + alignment: Alignment.center, + child: Icon(icon, size: size * 0.6, color: const Color(0xFFFFFFFF)), + ); + } +} diff --git a/mobile/lib/ui/atoms/app_text.dart b/mobile/lib/ui/atoms/app_text.dart new file mode 100644 index 0000000..6dede77 --- /dev/null +++ b/mobile/lib/ui/atoms/app_text.dart @@ -0,0 +1,90 @@ +import 'package:flutter/cupertino.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +/// Semantic text following the iOS type scale. +class AppText extends StatelessWidget { + const AppText( + this.data, { + super.key, + this.style = AppTypography.body, + this.color = AppColors.label, + this.maxLines, + this.overflow, + this.textAlign, + }); + + const AppText.largeTitle(this.data, {super.key, this.color = AppColors.label, this.maxLines, this.overflow, this.textAlign}) + : style = AppTypography.largeTitle; + + const AppText.title(this.data, {super.key, this.color = AppColors.label, this.maxLines, this.overflow, this.textAlign}) + : style = AppTypography.title2; + + const AppText.headline(this.data, {super.key, this.color = AppColors.label, this.maxLines, this.overflow, this.textAlign}) + : style = AppTypography.headline; + + const AppText.body(this.data, {super.key, this.color = AppColors.label, this.maxLines, this.overflow, this.textAlign}) + : style = AppTypography.body; + + const AppText.callout(this.data, {super.key, this.color = AppColors.secondaryLabel, this.maxLines, this.overflow, this.textAlign}) + : style = AppTypography.callout; + + const AppText.subhead(this.data, {super.key, this.color = AppColors.secondaryLabel, this.maxLines, this.overflow, this.textAlign}) + : style = AppTypography.subhead; + + const AppText.footnote(this.data, {super.key, this.color = AppColors.secondaryLabel, this.maxLines, this.overflow, this.textAlign}) + : style = AppTypography.footnote; + + const AppText.caption(this.data, {super.key, this.color = AppColors.tertiaryLabel, this.maxLines, this.overflow, this.textAlign}) + : style = AppTypography.caption1; + + final String data; + final TextStyle style; + final Color color; + final int? maxLines; + final TextOverflow? overflow; + final TextAlign? textAlign; + + @override + Widget build(BuildContext context) { + return Text( + data, + maxLines: maxLines, + overflow: overflow, + textAlign: textAlign, + style: GoogleFonts.inter( + textStyle: style, + color: AppColors.of(context, color), + ), + ); + } +} + +/// Uppercase grouped-list header, e.g. `НАСТРОЙКИ`. +class AppSectionHeader extends StatelessWidget { + const AppSectionHeader(this.text, {super.key, this.padding}); + + final String text; + final EdgeInsetsGeometry? padding; + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding ?? + const EdgeInsets.fromLTRB( + AppSpacing.gutter, + AppSpacing.s4, + AppSpacing.gutter, + AppSpacing.s2, + ), + child: Text( + text.toUpperCase(), + style: GoogleFonts.inter( + textStyle: AppTypography.footnote, + color: AppColors.of(context, AppColors.secondaryLabel), + letterSpacing: 0.4, + ), + ), + ); + } +} diff --git a/mobile/lib/ui/atoms/app_text_field.dart b/mobile/lib/ui/atoms/app_text_field.dart new file mode 100644 index 0000000..da84f8e --- /dev/null +++ b/mobile/lib/ui/atoms/app_text_field.dart @@ -0,0 +1,96 @@ +import 'package:flutter/cupertino.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:please_pay_me/theme/tokens.dart'; +import 'package:please_pay_me/ui/atoms/app_text.dart'; + +/// Rounded iOS text field with optional grouped-style caption and error. +class AppTextField extends StatelessWidget { + const AppTextField({ + super.key, + this.label, + this.placeholder, + this.controller, + this.onChanged, + this.keyboardType, + this.obscureText = false, + this.enabled = true, + this.errorText, + this.prefixIcon, + this.clearable = true, + this.maxLines = 1, + }); + + final String? label; + final String? placeholder; + final TextEditingController? controller; + final ValueChanged? onChanged; + final TextInputType? keyboardType; + final bool obscureText; + final bool enabled; + final String? errorText; + final IconData? prefixIcon; + final bool clearable; + final int maxLines; + + @override + Widget build(BuildContext context) { + final hasError = errorText != null && errorText!.isNotEmpty; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (label != null) AppSectionHeader(label!, padding: const EdgeInsets.only(bottom: AppSpacing.s2)), + Opacity( + opacity: enabled ? 1 : 0.4, + child: CupertinoTextField( + controller: controller, + onChanged: onChanged, + keyboardType: keyboardType, + obscureText: obscureText, + enabled: enabled, + maxLines: maxLines, + placeholder: placeholder, + clearButtonMode: clearable ? OverlayVisibilityMode.editing : OverlayVisibilityMode.never, + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.s3, + vertical: AppSpacing.s3, + ), + prefix: prefixIcon == null + ? null + : Padding( + padding: const EdgeInsets.only(left: AppSpacing.s3), + child: Icon( + prefixIcon, + size: 20, + color: AppColors.of(context, AppColors.secondaryLabel), + ), + ), + placeholderStyle: GoogleFonts.inter( + textStyle: AppTypography.body, + color: AppColors.of(context, AppColors.tertiaryLabel), + ), + style: GoogleFonts.inter( + textStyle: AppTypography.body, + color: AppColors.of(context, AppColors.label), + ), + decoration: BoxDecoration( + color: AppColors.of(context, AppColors.groupedSurface), + borderRadius: BorderRadius.circular(AppRadii.md), + border: Border.all( + color: hasError + ? AppColors.of(context, AppColors.systemRed) + : AppColors.of(context, AppColors.opaqueSeparator), + width: AppSizes.hairline, + ), + ), + ), + ), + if (hasError) + Padding( + padding: const EdgeInsets.only(top: AppSpacing.s2, left: AppSpacing.s1), + child: AppText.footnote(errorText!, color: AppColors.systemRed), + ), + ], + ); + } +} diff --git a/mobile/lib/ui/feedback/app_progress.dart b/mobile/lib/ui/feedback/app_progress.dart new file mode 100644 index 0000000..118abe2 --- /dev/null +++ b/mobile/lib/ui/feedback/app_progress.dart @@ -0,0 +1,56 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +/// Native iOS spinner. +class AppSpinner extends StatelessWidget { + const AppSpinner({super.key, this.radius = 12, this.color}); + + final double radius; + final Color? color; + + @override + Widget build(BuildContext context) { + return CupertinoActivityIndicator( + radius: radius, + color: color == null ? null : AppColors.of(context, color!), + ); + } +} + +/// iOS progress bar: 4pt capsule track. +class AppProgressBar extends StatelessWidget { + const AppProgressBar({ + super.key, + required this.value, + this.color = AppColors.accent, + this.height = 4, + }); + + /// 0..1 + final double value; + final Color color; + final double height; + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(AppRadii.capsule), + child: SizedBox( + height: height, + child: LayoutBuilder( + builder: (context, constraints) => Stack( + children: [ + Container(color: AppColors.of(context, AppColors.systemGray5)), + AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + width: constraints.maxWidth * value.clamp(0.0, 1.0), + color: AppColors.of(context, color), + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/ui/feedback/app_skeleton.dart b/mobile/lib/ui/feedback/app_skeleton.dart new file mode 100644 index 0000000..9a96fcb --- /dev/null +++ b/mobile/lib/ui/feedback/app_skeleton.dart @@ -0,0 +1,90 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +/// Pulsing placeholder block used while content loads. +class AppSkeleton extends StatefulWidget { + const AppSkeleton({ + super.key, + this.width, + this.height = 16, + this.radius = AppRadii.sm, + }); + + const AppSkeleton.circle({super.key, required double size}) + : width = size, + height = size, + radius = AppRadii.capsule; + + final double? width; + final double height; + final double radius; + + @override + State createState() => _AppSkeletonState(); +} + +class _AppSkeletonState extends State with SingleTickerProviderStateMixin { + late final AnimationController _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1100), + )..repeat(reverse: true); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: Tween(begin: 0.45, end: 1).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeInOut), + ), + child: Container( + width: widget.width, + height: widget.height, + decoration: BoxDecoration( + color: AppColors.of(context, AppColors.systemGray5), + borderRadius: BorderRadius.circular(widget.radius), + ), + ), + ); + } +} + +/// Skeleton shaped like an [AppListTile] row. +class AppSkeletonRow extends StatelessWidget { + const AppSkeletonRow({super.key, this.hasLeading = true}); + + final bool hasLeading; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.gutter, + vertical: AppSpacing.s3, + ), + child: Row( + children: [ + if (hasLeading) ...[ + const AppSkeleton.circle(size: 29), + const SizedBox(width: AppSpacing.s3), + ], + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppSkeleton(width: 140, height: 15), + SizedBox(height: AppSpacing.s2), + AppSkeleton(width: 90, height: 12), + ], + ), + ), + const AppSkeleton(width: 56, height: 15), + ], + ), + ); + } +} diff --git a/mobile/lib/ui/feedback/app_state_views.dart b/mobile/lib/ui/feedback/app_state_views.dart new file mode 100644 index 0000000..495858c --- /dev/null +++ b/mobile/lib/ui/feedback/app_state_views.dart @@ -0,0 +1,87 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; +import 'package:please_pay_me/ui/atoms/app_button.dart'; +import 'package:please_pay_me/ui/atoms/app_icon.dart'; +import 'package:please_pay_me/ui/atoms/app_text.dart'; +import 'package:please_pay_me/ui/feedback/app_progress.dart'; + +/// Centered placeholder for empty collections. +class AppEmptyState extends StatelessWidget { + const AppEmptyState({ + super.key, + required this.title, + this.message, + this.icon = CupertinoIcons.tray, + this.actionLabel, + this.onAction, + }); + + final String title; + final String? message; + final IconData icon; + final String? actionLabel; + final VoidCallback? onAction; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.s6, + vertical: AppSpacing.s7, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AppIcon(icon, size: 44, color: AppColors.tertiaryLabel), + const SizedBox(height: AppSpacing.s3), + AppText.headline(title, textAlign: TextAlign.center), + if (message != null) ...[ + const SizedBox(height: AppSpacing.s1), + AppText.subhead(message!, textAlign: TextAlign.center), + ], + if (actionLabel != null && onAction != null) ...[ + const SizedBox(height: AppSpacing.s4), + AppButton( + label: actionLabel!, + size: AppButtonSize.medium, + style: AppButtonStyle.tinted, + expanded: false, + onPressed: onAction, + ), + ], + ], + ), + ); + } +} + +/// Failure placeholder with a retry affordance. +class AppErrorView extends StatelessWidget { + const AppErrorView({super.key, required this.message, this.onRetry}); + + final String message; + final VoidCallback? onRetry; + + @override + Widget build(BuildContext context) { + return AppEmptyState( + icon: CupertinoIcons.exclamationmark_triangle, + title: 'Не получилось загрузить', + message: message, + actionLabel: onRetry == null ? null : 'Повторить', + onAction: onRetry, + ); + } +} + +class AppLoadingView extends StatelessWidget { + const AppLoadingView({super.key}); + + @override + Widget build(BuildContext context) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: AppSpacing.s7), + child: Center(child: AppSpinner()), + ); + } +} diff --git a/mobile/lib/ui/feedback/app_toast.dart b/mobile/lib/ui/feedback/app_toast.dart new file mode 100644 index 0000000..bed6185 --- /dev/null +++ b/mobile/lib/ui/feedback/app_toast.dart @@ -0,0 +1,77 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; +import 'package:please_pay_me/ui/atoms/app_text.dart'; + +/// iOS has no SnackBar — the platform idiom is a floating blurred capsule +/// (AirPods / Silent-mode style). Kept dismiss-free and auto-hiding. +class AppToast extends StatelessWidget { + const AppToast({ + super.key, + required this.message, + this.icon = CupertinoIcons.check_mark_circled_solid, + this.tint = AppColors.accent, + }); + + final String message; + final IconData? icon; + final Color tint; + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(AppRadii.capsule), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.s4, + vertical: AppSpacing.s3, + ), + decoration: BoxDecoration( + color: AppColors.of(context, AppColors.groupedSurface).withValues(alpha: 0.82), + borderRadius: BorderRadius.circular(AppRadii.capsule), + boxShadow: const [ + BoxShadow(color: Color(0x1F000000), blurRadius: 24, offset: Offset(0, 8)), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) ...[ + Icon(icon, size: 20, color: AppColors.of(context, tint)), + const SizedBox(width: AppSpacing.s2), + ], + Flexible(child: AppText.subhead(message, color: AppColors.label)), + ], + ), + ), + ), + ); + } +} + +Future showAppToast( + BuildContext context, { + required String message, + IconData? icon = CupertinoIcons.check_mark_circled_solid, + Duration duration = const Duration(seconds: 2), +}) async { + final overlay = Overlay.of(context, rootOverlay: true); + final entry = OverlayEntry( + builder: (ctx) => Positioned( + left: AppSpacing.s5, + right: AppSpacing.s5, + bottom: MediaQuery.of(ctx).padding.bottom + AppSpacing.s7, + child: SafeArea( + top: false, + child: Center(child: AppToast(message: message, icon: icon)), + ), + ), + ); + + overlay.insert(entry); + await Future.delayed(duration); + entry.remove(); +} diff --git a/mobile/lib/ui/molecules/app_avatar.dart b/mobile/lib/ui/molecules/app_avatar.dart new file mode 100644 index 0000000..f813e1d --- /dev/null +++ b/mobile/lib/ui/molecules/app_avatar.dart @@ -0,0 +1,49 @@ +import 'package:flutter/cupertino.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +/// Circular avatar — image, initials, or the iOS person placeholder. +class AppAvatar extends StatelessWidget { + const AppAvatar({ + super.key, + this.imageUrl, + this.initials, + this.radius = 22, + this.backgroundColor = AppColors.systemGray5, + }); + + final String? imageUrl; + final String? initials; + final double radius; + final Color backgroundColor; + + @override + Widget build(BuildContext context) { + final size = radius * 2; + final hasImage = imageUrl != null && imageUrl!.isNotEmpty; + + return ClipOval( + child: Container( + width: size, + height: size, + color: AppColors.of(context, backgroundColor), + alignment: Alignment.center, + child: hasImage + ? Image.network(imageUrl!, width: size, height: size, fit: BoxFit.cover) + : initials != null && initials!.isNotEmpty + ? Text( + initials!.toUpperCase(), + style: GoogleFonts.inter( + textStyle: AppTypography.headline.copyWith(fontSize: radius * 0.8), + color: AppColors.of(context, AppColors.secondaryLabel), + ), + ) + : Icon( + CupertinoIcons.person_fill, + size: radius, + color: AppColors.of(context, AppColors.systemGray), + ), + ), + ); + } +} diff --git a/mobile/lib/ui/molecules/app_card.dart b/mobile/lib/ui/molecules/app_card.dart new file mode 100644 index 0000000..8f83c09 --- /dev/null +++ b/mobile/lib/ui/molecules/app_card.dart @@ -0,0 +1,56 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; +import 'package:please_pay_me/ui/atoms/app_text.dart'; + +/// Inset-grouped card for free-form content (metrics, summaries). +class AppCard extends StatelessWidget { + const AppCard({ + super.key, + required this.child, + this.title, + this.subtitle, + this.padding = const EdgeInsets.all(AppSpacing.s4), + this.margin = const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + this.onTap, + }); + + final Widget child; + final String? title; + final String? subtitle; + final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry margin; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + final card = Container( + margin: margin, + padding: padding, + decoration: BoxDecoration( + color: AppColors.of(context, AppColors.groupedSurface), + borderRadius: BorderRadius.circular(AppRadii.lg), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (title != null) AppText.headline(title!), + if (subtitle != null) ...[ + const SizedBox(height: 2), + AppText.footnote(subtitle!), + ], + if (title != null || subtitle != null) const SizedBox(height: AppSpacing.s3), + child, + ], + ), + ); + + if (onTap == null) return card; + + return CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: onTap, + child: card, + ); + } +} diff --git a/mobile/lib/ui/molecules/app_chip.dart b/mobile/lib/ui/molecules/app_chip.dart new file mode 100644 index 0000000..fe06e08 --- /dev/null +++ b/mobile/lib/ui/molecules/app_chip.dart @@ -0,0 +1,59 @@ +import 'package:flutter/cupertino.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +/// Capsule tag / filter pill (iOS has no Material chip — this is the HIG-ish +/// equivalent used in Photos & Mail filters). +class AppChip extends StatelessWidget { + const AppChip({ + super.key, + required this.label, + this.selected = false, + this.icon, + this.onPressed, + }); + + final String label; + final bool selected; + final IconData? icon; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + final accent = AppColors.of(context, AppColors.accent); + final background = selected ? accent : AppColors.of(context, AppColors.systemGray5); + final foreground = selected ? const Color(0xFFFFFFFF) : AppColors.of(context, AppColors.label); + + return CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: onPressed, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.s3, + vertical: AppSpacing.s2 - 1, + ), + decoration: BoxDecoration( + color: background, + borderRadius: BorderRadius.circular(AppRadii.capsule), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) ...[ + Icon(icon, size: 15, color: foreground), + const SizedBox(width: AppSpacing.s1 + 2), + ], + Text( + label, + style: GoogleFonts.inter( + textStyle: AppTypography.subhead.copyWith(fontWeight: FontWeight.w500), + color: foreground, + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/ui/molecules/app_list_section.dart b/mobile/lib/ui/molecules/app_list_section.dart new file mode 100644 index 0000000..6366f65 --- /dev/null +++ b/mobile/lib/ui/molecules/app_list_section.dart @@ -0,0 +1,66 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; +import 'package:please_pay_me/ui/atoms/app_text.dart'; + +/// Inset-grouped section: rounded surface + hairline separators between rows. +class AppListSection extends StatelessWidget { + const AppListSection({ + super.key, + required this.children, + this.header, + this.footer, + this.separatorIndent = 16, + this.margin = const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + }); + + final List children; + final String? header; + final String? footer; + final double separatorIndent; + final EdgeInsetsGeometry margin; + + @override + Widget build(BuildContext context) { + final rows = []; + for (var i = 0; i < children.length; i++) { + rows.add(children[i]); + if (i < children.length - 1) { + rows.add( + Padding( + padding: EdgeInsets.only(left: separatorIndent), + child: Container( + height: AppSizes.hairline, + color: AppColors.of(context, AppColors.opaqueSeparator), + ), + ), + ); + } + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (header != null) AppSectionHeader(header!), + Container( + margin: margin, + decoration: BoxDecoration( + color: AppColors.of(context, AppColors.groupedSurface), + borderRadius: BorderRadius.circular(AppRadii.lg), + ), + clipBehavior: Clip.antiAlias, + child: Column(children: rows), + ), + if (footer != null) + Padding( + padding: const EdgeInsets.fromLTRB( + AppSpacing.gutter + 4, + AppSpacing.s2, + AppSpacing.gutter + 4, + 0, + ), + child: AppText.footnote(footer!), + ), + ], + ); + } +} diff --git a/mobile/lib/ui/molecules/app_list_tile.dart b/mobile/lib/ui/molecules/app_list_tile.dart new file mode 100644 index 0000000..1e9e278 --- /dev/null +++ b/mobile/lib/ui/molecules/app_list_tile.dart @@ -0,0 +1,110 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; +import 'package:please_pay_me/ui/atoms/app_text.dart'; + +/// Single row of an inset-grouped list (Settings-style). +class AppListTile extends StatefulWidget { + const AppListTile({ + super.key, + required this.title, + this.subtitle, + this.leading, + this.value, + this.trailing, + this.onTap, + this.showChevron = true, + this.destructive = false, + }); + + final String title; + final String? subtitle; + final Widget? leading; + + /// Secondary gray text aligned to the right (iOS `additionalInfo`). + final String? value; + + /// Custom trailing widget — replaces [value] and the chevron. + final Widget? trailing; + + final VoidCallback? onTap; + final bool showChevron; + final bool destructive; + + @override + State createState() => _AppListTileState(); +} + +class _AppListTileState extends State { + bool _pressed = false; + + @override + Widget build(BuildContext context) { + final tappable = widget.onTap != null; + + final row = Container( + color: _pressed + ? AppColors.of(context, AppColors.systemGray5) + : AppColors.of(context, AppColors.groupedSurface), + constraints: const BoxConstraints(minHeight: AppSizes.rowMinHeight), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.gutter, + vertical: AppSpacing.s2 + 2, + ), + child: Row( + children: [ + if (widget.leading != null) ...[ + widget.leading!, + const SizedBox(width: AppSpacing.s3), + ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AppText.body( + widget.title, + color: widget.destructive ? AppColors.systemRed : AppColors.label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (widget.subtitle != null) ...[ + const SizedBox(height: 2), + AppText.footnote(widget.subtitle!, maxLines: 2, overflow: TextOverflow.ellipsis), + ], + ], + ), + ), + if (widget.trailing != null) + widget.trailing! + else ...[ + if (widget.value != null) + Padding( + padding: const EdgeInsets.only(left: AppSpacing.s2), + child: AppText.body(widget.value!, color: AppColors.secondaryLabel), + ), + if (tappable && widget.showChevron) + Padding( + padding: const EdgeInsets.only(left: AppSpacing.s1), + child: Icon( + CupertinoIcons.chevron_forward, + size: 16, + color: AppColors.of(context, AppColors.tertiaryLabel), + ), + ), + ], + ], + ), + ); + + if (!tappable) return row; + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: widget.onTap, + onTapDown: (_) => setState(() => _pressed = true), + onTapUp: (_) => setState(() => _pressed = false), + onTapCancel: () => setState(() => _pressed = false), + child: row, + ); + } +} diff --git a/mobile/lib/ui/molecules/app_switch_row.dart b/mobile/lib/ui/molecules/app_switch_row.dart new file mode 100644 index 0000000..349db43 --- /dev/null +++ b/mobile/lib/ui/molecules/app_switch_row.dart @@ -0,0 +1,36 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; +import 'package:please_pay_me/ui/molecules/app_list_tile.dart'; + +/// Settings row with a native iOS switch on the trailing edge. +class AppSwitchRow extends StatelessWidget { + const AppSwitchRow({ + super.key, + required this.title, + required this.value, + required this.onChanged, + this.subtitle, + this.leading, + }); + + final String title; + final String? subtitle; + final Widget? leading; + final bool value; + final ValueChanged? onChanged; + + @override + Widget build(BuildContext context) { + return AppListTile( + title: title, + subtitle: subtitle, + leading: leading, + showChevron: false, + trailing: CupertinoSwitch( + value: value, + onChanged: onChanged, + activeTrackColor: AppColors.of(context, AppColors.accent), + ), + ); + } +} diff --git a/mobile/lib/ui/navigation/app_dialog.dart b/mobile/lib/ui/navigation/app_dialog.dart new file mode 100644 index 0000000..b7b745f --- /dev/null +++ b/mobile/lib/ui/navigation/app_dialog.dart @@ -0,0 +1,107 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/ui/atoms/app_text.dart'; + +/// Native iOS alert. +class AppAlert extends StatelessWidget { + const AppAlert({ + super.key, + required this.title, + this.message, + this.confirmLabel = 'OK', + this.cancelLabel, + this.destructive = false, + }); + + final String title; + final String? message; + final String confirmLabel; + final String? cancelLabel; + final bool destructive; + + @override + Widget build(BuildContext context) { + return CupertinoAlertDialog( + title: AppText.headline(title), + content: message == null + ? null + : Padding( + padding: const EdgeInsets.only(top: 6), + child: AppText.subhead(message!, textAlign: TextAlign.center), + ), + actions: [ + if (cancelLabel != null) + CupertinoDialogAction( + onPressed: () => Navigator.of(context).pop(false), + child: Text(cancelLabel!), + ), + CupertinoDialogAction( + isDefaultAction: !destructive, + isDestructiveAction: destructive, + onPressed: () => Navigator.of(context).pop(true), + child: Text(confirmLabel), + ), + ], + ); + } +} + +Future showAppAlert({ + required BuildContext context, + required String title, + String? message, + String confirmLabel = 'OK', + String? cancelLabel, + bool destructive = false, +}) { + return showCupertinoDialog( + context: context, + builder: (_) => AppAlert( + title: title, + message: message, + confirmLabel: confirmLabel, + cancelLabel: cancelLabel, + destructive: destructive, + ), + ); +} + +class AppActionSheetAction { + const AppActionSheetAction({ + required this.label, + this.destructive = false, + this.isDefault = false, + }); + + final String label; + final bool destructive; + final bool isDefault; +} + +Future showAppActionSheet({ + required BuildContext context, + String? title, + String? message, + required List actions, + String cancelLabel = 'Отмена', +}) { + return showCupertinoModalPopup( + context: context, + builder: (ctx) => CupertinoActionSheet( + title: title == null ? null : Text(title), + message: message == null ? null : Text(message), + actions: [ + for (var i = 0; i < actions.length; i++) + CupertinoActionSheetAction( + isDestructiveAction: actions[i].destructive, + isDefaultAction: actions[i].isDefault, + onPressed: () => Navigator.of(ctx).pop(i), + child: Text(actions[i].label), + ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(ctx).pop(), + child: Text(cancelLabel), + ), + ), + ); +} diff --git a/mobile/lib/ui/navigation/app_nav_bar.dart b/mobile/lib/ui/navigation/app_nav_bar.dart new file mode 100644 index 0000000..e8d5945 --- /dev/null +++ b/mobile/lib/ui/navigation/app_nav_bar.dart @@ -0,0 +1,86 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; +import 'package:please_pay_me/ui/atoms/app_text.dart'; + +/// Standard iOS navigation bar (44pt) with optional subtitle line. +class AppNavBar extends StatelessWidget implements ObstructingPreferredSizeWidget { + const AppNavBar({ + super.key, + required this.title, + this.subtitle, + this.leading, + this.trailing, + this.previousPageTitle, + this.transparent = false, + }); + + final String title; + final String? subtitle; + final Widget? leading; + final Widget? trailing; + final String? previousPageTitle; + final bool transparent; + + @override + Size get preferredSize => const Size.fromHeight(44); + + @override + bool shouldFullyObstruct(BuildContext context) => !transparent; + + @override + Widget build(BuildContext context) { + return CupertinoNavigationBar( + leading: leading, + trailing: trailing, + previousPageTitle: previousPageTitle, + backgroundColor: transparent + ? const Color(0x00000000) + : AppColors.of(context, AppColors.barBackground), + border: transparent + ? null + : Border( + bottom: BorderSide( + color: AppColors.of(context, AppColors.separator), + width: AppSizes.hairline, + ), + ), + middle: subtitle == null + ? AppText.headline(title) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + AppText.headline(title, maxLines: 1, overflow: TextOverflow.ellipsis), + AppText.caption( + subtitle!, + color: AppColors.secondaryLabel, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ); + } +} + +/// Large-title bar for the root of a scrollable screen. +class AppLargeNavBar extends StatelessWidget { + const AppLargeNavBar({super.key, required this.title, this.trailing}); + + final String title; + final Widget? trailing; + + @override + Widget build(BuildContext context) { + return CupertinoSliverNavigationBar( + largeTitle: AppText.largeTitle(title), + trailing: trailing, + backgroundColor: AppColors.of(context, AppColors.barBackground), + border: Border( + bottom: BorderSide( + color: AppColors.of(context, AppColors.separator), + width: AppSizes.hairline, + ), + ), + ); + } +} diff --git a/mobile/lib/ui/navigation/app_pickers.dart b/mobile/lib/ui/navigation/app_pickers.dart new file mode 100644 index 0000000..71dc051 --- /dev/null +++ b/mobile/lib/ui/navigation/app_pickers.dart @@ -0,0 +1,70 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; +import 'package:please_pay_me/ui/atoms/app_button.dart'; + +/// Bottom sheet with a native wheel date picker. +Future showAppDatePicker({ + required BuildContext context, + required DateTime initialDate, + DateTime? minimumDate, + DateTime? maximumDate, + String confirmLabel = 'Готово', +}) { + var selected = initialDate; + + return showCupertinoModalPopup( + context: context, + builder: (ctx) => Container( + height: 320, + padding: const EdgeInsets.only(top: AppSpacing.s2), + color: AppColors.of(ctx, AppColors.groupedSurface), + child: SafeArea( + top: false, + child: Column( + children: [ + Expanded( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + initialDateTime: initialDate, + minimumDate: minimumDate, + maximumDate: maximumDate, + onDateTimeChanged: (value) => selected = value, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB( + AppSpacing.gutter, + AppSpacing.s2, + AppSpacing.gutter, + AppSpacing.s3, + ), + child: AppButton( + label: confirmLabel, + onPressed: () => Navigator.of(ctx).pop( + DateTime(selected.year, selected.month, selected.day), + ), + ), + ), + ], + ), + ), + ), + ); +} + +/// Full-height modal used for create/edit forms. +Future showAppFormSheet({ + required BuildContext context, + required WidgetBuilder builder, +}) { + return showCupertinoModalPopup( + context: context, + builder: (ctx) => Padding( + padding: EdgeInsets.only(top: MediaQuery.of(ctx).padding.top + AppSpacing.s6), + child: ClipRRect( + borderRadius: const BorderRadius.vertical(top: Radius.circular(AppRadii.xl)), + child: Builder(builder: builder), + ), + ), + ); +} diff --git a/mobile/lib/ui/navigation/app_segmented_control.dart b/mobile/lib/ui/navigation/app_segmented_control.dart new file mode 100644 index 0000000..9f2f4d2 --- /dev/null +++ b/mobile/lib/ui/navigation/app_segmented_control.dart @@ -0,0 +1,54 @@ +import 'package:flutter/cupertino.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +/// iOS sliding segmented control — the native alternative to tabs. +class AppSegmentedControl extends StatelessWidget { + const AppSegmentedControl({ + super.key, + required this.labels, + required this.index, + required this.onChanged, + this.padding = const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), + }); + + final List labels; + final int index; + final ValueChanged onChanged; + final EdgeInsetsGeometry padding; + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding, + child: SizedBox( + width: double.infinity, + child: CupertinoSlidingSegmentedControl( + groupValue: index.clamp(0, labels.length - 1), + backgroundColor: AppColors.of(context, AppColors.systemGray5), + thumbColor: AppColors.of(context, AppColors.groupedSurface), + onValueChanged: (value) { + if (value != null) onChanged(value); + }, + children: { + for (var i = 0; i < labels.length; i++) + i: Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Text( + labels[i], + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.inter( + textStyle: AppTypography.subhead.copyWith( + fontWeight: i == index ? FontWeight.w600 : FontWeight.w400, + ), + color: AppColors.of(context, AppColors.label), + ), + ), + ), + }, + ), + ), + ); + } +} diff --git a/mobile/lib/ui/navigation/app_tab_bar.dart b/mobile/lib/ui/navigation/app_tab_bar.dart new file mode 100644 index 0000000..ccaa0ef --- /dev/null +++ b/mobile/lib/ui/navigation/app_tab_bar.dart @@ -0,0 +1,38 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/tokens.dart'; + +class AppTabItem { + const AppTabItem({required this.icon, required this.label, this.activeIcon}); + + final IconData icon; + final IconData? activeIcon; + final String label; +} + +/// Bottom tab bar (iOS): 49pt, hairline top border, tint = brand accent. +/// +/// Extends [CupertinoTabBar] so it can be passed to [CupertinoTabScaffold]; +/// dynamic colors are resolved by the base class against the active theme. +class AppTabBar extends CupertinoTabBar { + AppTabBar({ + super.key, + required List items, + required super.currentIndex, + required ValueChanged super.onTap, + }) : super( + items: [ + for (final item in items) + BottomNavigationBarItem( + icon: Icon(item.icon), + activeIcon: item.activeIcon == null ? null : Icon(item.activeIcon), + label: item.label, + ), + ], + activeColor: AppColors.accent, + inactiveColor: AppColors.systemGray, + backgroundColor: AppColors.barBackground, + border: const Border( + top: BorderSide(color: AppColors.separator, width: AppSizes.hairline), + ), + ); +} diff --git a/mobile/lib/ui/ui.dart b/mobile/lib/ui/ui.dart new file mode 100644 index 0000000..8510515 --- /dev/null +++ b/mobile/lib/ui/ui.dart @@ -0,0 +1,20 @@ +export 'atoms/app_brand_mark.dart'; +export 'atoms/app_button.dart'; +export 'atoms/app_icon.dart'; +export 'atoms/app_text.dart'; +export 'atoms/app_text_field.dart'; +export 'feedback/app_progress.dart'; +export 'feedback/app_skeleton.dart'; +export 'feedback/app_state_views.dart'; +export 'feedback/app_toast.dart'; +export 'molecules/app_avatar.dart'; +export 'molecules/app_card.dart'; +export 'molecules/app_chip.dart'; +export 'molecules/app_list_section.dart'; +export 'molecules/app_list_tile.dart'; +export 'molecules/app_switch_row.dart'; +export 'navigation/app_dialog.dart'; +export 'navigation/app_nav_bar.dart'; +export 'navigation/app_pickers.dart'; +export 'navigation/app_segmented_control.dart'; +export 'navigation/app_tab_bar.dart'; diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock new file mode 100644 index 0000000..9211e37 --- /dev/null +++ b/mobile/pubspec.lock @@ -0,0 +1,687 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + sha256: "6c5bcd986e06b94e3c40244af471750840a3d2341d1f9763a1100a14add517b4" + url: "https://pub.dev" + source: hosted + version: "4.3.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e + url: "https://pub.dev" + source: hosted + version: "1.1.3" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.dev" + source: hosted + version: "0.14.4" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055 + url: "https://pub.dev" + source: hosted + version: "6.3.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: a1e7f4951e538a568e14b856702afc9ae1d2f4b202daced8d22c1b9cd211ce89 + url: "https://pub.dev" + source: hosted + version: "4.10.1" + intl: + dependency: "direct main" + description: + name: intl + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" + url: "https://pub.dev" + source: hosted + version: "0.20.3" + jni: + dependency: transitive + description: + name: jni + sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: b2310cdd4c18c65c081ab141a41efa94aa26c65431803703ece51996f174f351 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_util: + dependency: transitive + description: + name: jni_util + sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" + url: "https://pub.dev" + source: hosted + version: "0.12.20" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d + url: "https://pub.dev" + source: hosted + version: "1.18.3" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e + url: "https://pub.dev" + source: hosted + version: "9.5.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d + url: "https://pub.dev" + source: hosted + version: "3.0.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec + url: "https://pub.dev" + source: hosted + version: "3.2.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e + url: "https://pub.dev" + source: hosted + version: "6.5.2" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "1e12aafe408aa50da80edfd679a2a6bf63ba7ab37c7fa98286da459a757b3399" + url: "https://pub.dev" + source: hosted + version: "2.4.28" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea" + url: "https://pub.dev" + source: hosted + version: "2.5.7" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490" + url: "https://pub.dev" + source: hosted + version: "1.12.2" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" + url: "https://pub.dev" + source: hosted + version: "0.7.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "611e87fb320b70d1dd721dc46af89c98aceccea9b31fde49e084591414e0c610" + url: "https://pub.dev" + source: hosted + version: "6.3.33" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "8faa1aab294f1ab4040b43660c887b0418d5fa4f0cffef76a484e6aa1092eb4a" + url: "https://pub.dev" + source: hosted + version: "6.4.2" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "10f86fef4c2c43563fa6c211ff9cf757adf4d3ab762c56bd430664a947d70cd0" + url: "https://pub.dev" + source: hosted + version: "3.2.3" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "5e835a3b869c2d70325349c81c5a45c28e20791265b67b2669da6b08c5cd5201" + url: "https://pub.dev" + source: hosted + version: "3.2.6" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "6c5ad3f22cd4c38e089b81963b3cd7bb83b111b2df5dce008bb066162f42e429" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "1d774bbdf6b72a0b12122fc1560c9c2d2a67db5a4a4cc2bd8a5c990ab20e3188" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" + url: "https://pub.dev" + source: hosted + version: "15.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + sha256: d53e1ccf5516f25017e3c9d44c39034db352d20fa34fe200674270242c2c5111 + url: "https://pub.dev" + source: hosted + version: "4.14.1" + webview_flutter_android: + dependency: "direct main" + description: + name: webview_flutter_android + sha256: "4de8b3d1ff4ebe1bdb42e68a5e4f809194a3cb0117a8f495f590004f00da3964" + url: "https://pub.dev" + source: hosted + version: "4.14.1" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04" + url: "https://pub.dev" + source: hosted + version: "2.15.1" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: fe359c7fac1002124b5b9e2ba3a41906bbb9b2d029ccb4a0067404d8f3704730 + url: "https://pub.dev" + source: hosted + version: "3.26.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea + url: "https://pub.dev" + source: hosted + version: "3.1.4" +sdks: + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml new file mode 100644 index 0000000..3d9674d --- /dev/null +++ b/mobile/pubspec.yaml @@ -0,0 +1,44 @@ +name: please_pay_me +description: Дожить до ЗП — бюджет от зарплаты до зарплаты +publish_to: "none" +version: 0.1.0+1 + +environment: + sdk: ">=3.5.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + intl: any + cupertino_icons: ^1.0.8 + google_fonts: ^6.2.1 + http: ^1.2.2 + provider: ^6.1.2 + shared_preferences: ^2.3.2 + url_launcher: ^6.3.0 + webview_flutter: ^4.10.0 + webview_flutter_android: ^4.10.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + flutter_launcher_icons: ^0.14.4 + +flutter: + uses-material-design: true + assets: + - assets/branding/app_icon.png + +flutter_launcher_icons: + android: true + ios: true + windows: + generate: true + icon_size: 256 + image_path: assets/branding/app_icon.png + adaptive_icon_background: "#12885A" + adaptive_icon_foreground: assets/branding/app_icon.png + min_sdk_android: 21 diff --git a/mobile/run.ps1 b/mobile/run.ps1 new file mode 100644 index 0000000..e942b9a --- /dev/null +++ b/mobile/run.ps1 @@ -0,0 +1,102 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Run the Please Pay Me Flutter app. + + Config comes from mobile/.env (PPM_API_BASE_URL, PPM_WEB_URL, PPM_DEMO). + Optional -ApiBaseUrl / -WebUrl override the file for one run. + +.EXAMPLE + .\run.ps1 + .\run.ps1 -Device chrome + .\run.ps1 -Target widgetbook +#> +param( + [ValidateSet("chrome", "windows", "edge")] + [string]$Device = "windows", + + [ValidateSet("app", "widgetbook")] + [string]$Target = "app", + + [string]$ApiBaseUrl = "", + [string]$WebUrl = "", + [switch]$SkipPubGet +) + +$ErrorActionPreference = "Stop" +$MobileRoot = Split-Path -Parent $MyInvocation.MyCommand.Path + +if ($Target -eq "widgetbook") { + & (Join-Path $MobileRoot "widgetbook\run.ps1") -Device $Device -SkipPubGet:$SkipPubGet + exit $LASTEXITCODE +} + +Set-Location $MobileRoot + +function Find-Flutter { + $cmd = Get-Command flutter -ErrorAction SilentlyContinue + if ($cmd) { return $cmd.Source } + + $candidates = @( + "$env:USERPROFILE\flutter\bin\flutter.bat", + "$env:LOCALAPPDATA\flutter\bin\flutter.bat", + "C:\flutter\bin\flutter.bat", + "C:\src\flutter\bin\flutter.bat" + ) + foreach ($path in $candidates) { + if (Test-Path $path) { return $path } + } + return $null +} + +$flutter = Find-Flutter +if (-not $flutter) { + Write-Error "Flutter SDK not found. Add it to PATH, e.g. %USERPROFILE%\flutter\bin" +} + +$envFile = Join-Path $MobileRoot ".env" +$exampleFile = Join-Path $MobileRoot ".env.example" +if (-not (Test-Path $envFile)) { + if (Test-Path $exampleFile) { + Copy-Item $exampleFile $envFile + Write-Host "Created .env from .env.example" -ForegroundColor DarkYellow + } +} + +$defines = @() +if (Test-Path $envFile) { + $defines += "--dart-define-from-file=$envFile" +} + +if ($ApiBaseUrl) { + $defines += "--dart-define=PPM_API_BASE_URL=$ApiBaseUrl" + if (-not $WebUrl) { $WebUrl = $ApiBaseUrl } +} +if ($WebUrl) { + $defines += "--dart-define=PPM_WEB_URL=$WebUrl" +} + +if ($defines.Count -eq 0) { + $defines += "--dart-define=PPM_DEMO=true" +} + +$mode = "demo (in-memory)" +if (Test-Path $envFile) { $mode = $envFile } +if ($ApiBaseUrl) { $mode = $ApiBaseUrl } + +Write-Host "Flutter: $flutter" -ForegroundColor DarkGray +Write-Host "Target: app" -ForegroundColor DarkGray +Write-Host "Device: $Device" -ForegroundColor DarkGray +Write-Host "Config: $mode" -ForegroundColor DarkGray +Write-Host "" + +if (-not $SkipPubGet) { + Write-Host "flutter pub get" -ForegroundColor Cyan + & $flutter pub get + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} + +$defineArgs = $defines -join " " +Write-Host "flutter run -d $Device $defineArgs" -ForegroundColor Cyan +& $flutter run -d $Device @defines +exit $LASTEXITCODE diff --git a/mobile/test/auth_test.dart b/mobile/test/auth_test.dart new file mode 100644 index 0000000..aa9dd0e --- /dev/null +++ b/mobile/test/auth_test.dart @@ -0,0 +1,268 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:please_pay_me/core/config/app_config.dart'; +import 'package:please_pay_me/core/storage/session_storage.dart'; +import 'package:please_pay_me/features/auth/login_screen.dart'; +import 'package:please_pay_me/features/auth/session_controller.dart'; +import 'package:please_pay_me/features/auth/telegram_login.dart'; +import 'package:please_pay_me/features/auth/yandex_login.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:provider/provider.dart'; + +import 'support/test_setup.dart'; + +void main() { + setUpAll(setUpTestEnvironment); + + group('resolveApiBaseUrl', () { + test('an explicitly configured API address wins', () { + expect( + resolveApiBaseUrl( + cabinetUrl: 'https://cabinet.example.com', + configuredApiBaseUrl: 'https://api.example.com/', + ), + 'https://api.example.com', + ); + }); + + test('falls back to the cabinet origin, dropping the path', () { + expect( + resolveApiBaseUrl(cabinetUrl: 'https://ppm.example.com/cabinet/budgets'), + 'https://ppm.example.com', + ); + }); + + test('keeps a non-default port', () { + expect( + resolveApiBaseUrl(cabinetUrl: 'http://192.168.0.10:8080/'), + 'http://192.168.0.10:8080', + ); + }); + + test('assumes https when the scheme is omitted', () { + expect(resolveApiBaseUrl(cabinetUrl: 'ppm.example.com/'), 'https://ppm.example.com'); + }); + }); + + group('resolveCabinetLoginUri', () { + test('opens /login on the cabinet origin', () { + expect( + resolveCabinetLoginUri('https://ppm.example.com'), + Uri.parse('https://ppm.example.com/login'), + ); + }); + + test('keeps an explicit path', () { + expect( + resolveCabinetLoginUri('https://ppm.example.com/cabinet'), + Uri.parse('https://ppm.example.com/cabinet'), + ); + }); + }); + + group('navigation helpers', () { + test('detects Telegram deep links', () { + expect(isExternalAuthScheme(Uri.parse('tg://resolve?domain=bot')), isTrue); + expect(isExternalAuthScheme(Uri.parse('https://oauth.telegram.org/auth')), isFalse); + }); + + test('detects Telegram OAuth hosts', () { + expect(isTelegramOAuthHost('oauth.telegram.org'), isTrue); + expect(isTelegramOAuthHost('ppm.example.com'), isFalse); + }); + }); + + group('telegram auth bridge', () { + test('probe script reads the key the web cabinet writes', () { + // Must stay in sync with SESSION_KEY in web/src/api.ts. + expect(telegramTokenProbeJs, contains("getItem('ppm_session_jwt')")); + expect(telegramTokenProbeJs, contains('$telegramAuthChannel.postMessage')); + }); + + test('parses the posted payload', () { + expect(parseTelegramAuthMessage(jsonEncode({'token': 'jwt'})), 'jwt'); + }); + + test('ignores empty and malformed payloads', () { + expect(parseTelegramAuthMessage(jsonEncode({'token': ''})), isNull); + expect(parseTelegramAuthMessage('not json'), isNull); + }); + }); + + group('yandex oauth helpers', () { + test('builds the authorize URL', () { + final uri = yandexAuthorizeUri( + clientId: 'abc', + redirectUri: 'https://please-pay-me.ru/', + ); + expect(uri.host, 'oauth.yandex.ru'); + expect(uri.queryParameters['client_id'], 'abc'); + expect(uri.queryParameters['redirect_uri'], 'https://please-pay-me.ru/'); + expect(uri.queryParameters['response_type'], 'code'); + }); + + test('reads a successful cabinet callback on the origin', () { + final callback = parseYandexCallback( + Uri.parse('https://please-pay-me.ru/?code=from-yandex'), + redirectUri: 'https://please-pay-me.ru/', + ); + expect(callback?.code, 'from-yandex'); + expect(callback?.error, isNull); + }); + + test('reads a denied callback and ignores other hosts', () { + expect( + parseYandexCallback( + Uri.parse('https://please-pay-me.ru/?error=access_denied'), + redirectUri: 'https://please-pay-me.ru/', + )?.error, + 'access_denied', + ); + expect( + parseYandexCallback( + Uri.parse('https://oauth.yandex.ru/authorize?code=nope'), + redirectUri: 'https://please-pay-me.ru/', + ), + isNull, + ); + }); + + test('derives the origin slash that Yandex registered', () { + expect( + cabinetYandexRedirectUri('https://please-pay-me.ru/budgets'), + 'https://please-pay-me.ru/', + ); + expect( + cabinetYandexRedirectUri( + 'https://please-pay-me.ru', + configured: 'https://please-pay-me.ru/', + ), + 'https://please-pay-me.ru/', + ); + }); + }); + + group('SessionController.signInWithToken', () { + SessionController controllerWith(http.Client client, SessionStorage storage) { + return SessionController( + config: AppConfig.demo, + storage: storage, + httpClient: client, + ); + } + + test('stores the session after the token is validated', () async { + late Uri requestedUri; + String? authHeader; + + final client = MockClient((request) async { + requestedUri = request.url; + authHeader = request.headers['Authorization']; + return http.Response( + jsonEncode({'user_id': 7, 'username': 'vlad', 'first_name': 'Владимир'}), + 200, + headers: {'content-type': 'application/json'}, + ); + }); + + final storage = InMemorySessionStorage(); + final session = controllerWith(client, storage); + + final ok = await session.signInWithToken( + baseUrl: 'https://ppm.example.com/', + token: 'jwt-token', + ); + + expect(ok, isTrue); + expect(session.status, SessionStatus.signedIn); + expect(session.user?.username, 'vlad'); + expect(requestedUri.toString(), 'https://ppm.example.com/api/me'); + expect(authHeader, 'Bearer jwt-token'); + expect((await storage.readAll())['token'], 'jwt-token'); + }); + + test('a rejected token leaves the user signed out with a message', () async { + final client = MockClient((_) async => http.Response('', 401)); + final session = controllerWith(client, InMemorySessionStorage()); + + final ok = await session.signInWithToken( + baseUrl: 'https://ppm.example.com', + token: 'stale', + ); + + expect(ok, isFalse); + expect(session.status, SessionStatus.signedOut); + expect(session.lastError, isNotNull); + }); + }); + + testWidgets('Yandex login exchanges the code on the API', (tester) async { + String? seenClientId; + String? seenRedirect; + + final client = MockClient((request) async { + if (request.url.path.endsWith('/api/auth/providers')) { + return http.Response( + jsonEncode({ + 'yandex': {'enabled': true, 'client_id': 'ya-client'}, + }), + 200, + headers: {'content-type': 'application/json'}, + ); + } + return http.Response( + jsonEncode({'user_id': 7, 'username': 'ya-user'}), + 200, + headers: {'content-type': 'application/json'}, + ); + }); + + final session = SessionController( + config: const AppConfig( + apiBaseUrl: 'https://ppm.example.com', + webCabinetUrl: 'https://ppm.example.com', + demoMode: false, + ), + storage: InMemorySessionStorage(), + httpClient: client, + ); + + await tester.pumpWidget( + ChangeNotifierProvider.value( + value: session, + child: CupertinoApp( + theme: buildLightTheme(), + home: LoginScreen( + launchTelegramLogin: (_, __) async => null, + launchYandexLogin: (_, {required clientId, required redirectUri}) async { + seenClientId = clientId; + seenRedirect = redirectUri; + return 'jwt-from-yandex'; + }, + ), + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); + + await tester.ensureVisible(find.byKey(const Key('legal-offer-check'))); + await tester.tap(find.byKey(const Key('legal-offer-check'))); + await tester.pump(); + await tester.ensureVisible(find.byKey(const Key('legal-consent-check'))); + await tester.tap(find.byKey(const Key('legal-consent-check'))); + await tester.pump(); + await tester.tap(find.text('Войти через Яндекс')); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 300)); + + expect(seenClientId, 'ya-client'); + expect(seenRedirect, 'https://ppm.example.com/'); + expect(session.status, SessionStatus.signedIn); + expect(session.user?.username, 'ya-user'); + }); +} diff --git a/mobile/test/config_test.dart b/mobile/test/config_test.dart new file mode 100644 index 0000000..4c3468a --- /dev/null +++ b/mobile/test/config_test.dart @@ -0,0 +1,68 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:please_pay_me/core/config/app_config.dart'; +import 'package:please_pay_me/core/config/env_file.dart'; + +void main() { + group('parseEnvFile', () { + test('reads keys, comments and quoted values', () { + const source = ''' +# comment +PPM_API_BASE_URL=https://please-pay-me.ru/ +PPM_WEB_URL="https://please-pay-me.ru" +PPM_DEMO=false + +EMPTY= +'''; + + final values = parseEnvFile(source); + + expect(values['PPM_API_BASE_URL'], 'https://please-pay-me.ru/'); + expect(values['PPM_WEB_URL'], 'https://please-pay-me.ru'); + expect(values['PPM_DEMO'], 'false'); + expect(values.containsKey('EMPTY'), isTrue); + expect(values['EMPTY'], ''); + }); + }); + + group('AppConfig.fromMap', () { + test('strips a trailing slash and defaults the cabinet URL', () { + final config = AppConfig.fromMap({ + 'PPM_API_BASE_URL': 'https://please-pay-me.ru/', + }); + + expect(config.apiBaseUrl, 'https://please-pay-me.ru'); + expect(config.webCabinetUrl, 'https://please-pay-me.ru'); + expect(config.demoMode, isFalse); + }); + + test('empty map uses the production cabinet', () { + final config = AppConfig.fromMap(const {}); + + expect(config.apiBaseUrl, AppConfig.productionOrigin); + expect(config.webCabinetUrl, AppConfig.productionOrigin); + expect(config.demoMode, isFalse); + }); + + test('PPM_DEMO=true forces demo even when a URL is set', () { + final config = AppConfig.fromMap({ + 'PPM_API_BASE_URL': 'https://please-pay-me.ru', + 'PPM_DEMO': 'true', + }); + + expect(config.demoMode, isTrue); + expect(config.apiBaseUrl, 'https://please-pay-me.ru'); + }); + }); + + test('.env.example stays in sync with the parser', () { + final text = File('.env.example').readAsStringSync(); + final values = parseEnvFile(text); + final config = AppConfig.fromMap(values); + + expect(values.containsKey('PPM_API_BASE_URL'), isTrue); + expect(config.apiBaseUrl, isNotEmpty); + expect(config.demoMode, isFalse); + }); +} diff --git a/mobile/test/controllers_test.dart b/mobile/test/controllers_test.dart new file mode 100644 index 0000000..bebeff5 --- /dev/null +++ b/mobile/test/controllers_test.dart @@ -0,0 +1,188 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:please_pay_me/core/config/app_config.dart'; +import 'package:please_pay_me/core/storage/session_storage.dart'; +import 'package:please_pay_me/data/demo/demo_backend.dart'; +import 'package:please_pay_me/features/auth/session_controller.dart'; +import 'package:please_pay_me/features/budgets/budgets_controller.dart'; +import 'package:please_pay_me/features/journal/journal_controller.dart'; +import 'package:please_pay_me/features/work/jobs_controller.dart'; + +void main() { + late DemoBackend backend; + + setUp(() => backend = DemoBackend(today: DateTime(2026, 9, 19))); + + group('BudgetsController', () { + test('loads budgets and exposes the selected one', () async { + final controller = BudgetsController( + budgets: backend.budgets, + expenses: backend.expenses, + ); + await controller.load(); + + expect(controller.items, hasLength(2)); + expect(controller.selected?.budget.name, 'До аванса'); + expect(controller.hasBudgets, isTrue); + }); + + test('adding an expense lowers the remaining amount', () async { + final controller = BudgetsController( + budgets: backend.budgets, + expenses: backend.expenses, + ); + await controller.load(); + final before = controller.selected!.remaining; + + final error = await controller.addExpense(amount: 500, note: 'Обед'); + + expect(error, isNull); + expect(controller.selected!.remaining, before - 500); + expect(controller.isMutating, isFalse); + }); + + test('undo removes the newest expense of the selected budget', () async { + final controller = BudgetsController( + budgets: backend.budgets, + expenses: backend.expenses, + ); + await controller.load(); + await controller.addExpense(amount: 777); + final withExtra = controller.selected!.totalSpent; + + await controller.undoLastExpense(); + + expect(controller.selected!.totalSpent, withExtra - 777); + }); + + test('select switches the current envelope', () async { + final controller = BudgetsController( + budgets: backend.budgets, + expenses: backend.expenses, + ); + await controller.load(); + + await controller.select(2); + + expect(controller.selected?.budget.id, 2); + }); + + test('surfaces backend errors without crashing', () async { + final empty = DemoBackend.empty(); + final controller = BudgetsController( + budgets: empty.budgets, + expenses: empty.expenses, + ); + await controller.load(); + + final error = await controller.addExpense(amount: 100); + + expect(error, 'Сначала создайте бюджет'); + expect(controller.hasBudgets, isFalse); + }); + }); + + group('JournalController', () { + test('groups operations by day, newest first', () async { + final controller = JournalController(expenses: backend.expenses); + await controller.load(); + + final groups = controller.groups; + + expect(groups.first.day, DateTime(2026, 9, 19)); + expect(groups.first.items, hasLength(2)); + expect(groups.first.total, 2090); + expect(groups.map((g) => g.day), isNot(contains(DateTime(2026, 8, 30)))); + }); + + test('scope "all" includes other budgets', () async { + final controller = JournalController(expenses: backend.expenses); + await controller.load(); + final currentCount = controller.items.length; + + await controller.setScope(JournalScope.all); + + expect(controller.items.length, greaterThan(currentCount)); + expect(controller.scope, JournalScope.all); + }); + + test('loadMore appends the next page', () async { + final controller = JournalController(expenses: backend.expenses, pageSize: 2); + await controller.load(); + + expect(controller.items, hasLength(2)); + expect(controller.hasMore, isTrue); + + await controller.loadMore(); + + expect(controller.items, hasLength(4)); + expect(controller.hasMore, isFalse); + }); + }); + + group('JobsController', () { + test('nextPay picks the closest payday', () async { + final controller = JobsController(jobs: backend.jobs); + await controller.load(); + + expect(controller.items, hasLength(1)); + expect(controller.nextPay?.date, DateTime(2026, 9, 27)); + }); + }); + + group('SessionController', () { + test('restores a stored session', () async { + final session = SessionController( + config: AppConfig.demo, + storage: InMemorySessionStorage({ + 'token': 'jwt', + 'baseUrl': 'https://ppm.example.com', + 'user': '{"user_id":7,"username":"vlad"}', + }), + ); + + await session.restore(); + + expect(session.status, SessionStatus.signedIn); + expect(session.user?.userId, 7); + expect(session.isDemo, isFalse); + }); + + test('without a stored token the app asks to sign in', () async { + final session = SessionController( + config: AppConfig.demo, + storage: InMemorySessionStorage(), + ); + + await session.restore(); + + expect(session.status, SessionStatus.signedOut); + }); + + test('demo mode serves repositories from the in-memory backend', () async { + final session = SessionController( + config: AppConfig.demo, + storage: InMemorySessionStorage(), + demoBackend: backend, + )..startDemo(); + + expect(session.status, SessionStatus.signedIn); + expect(await session.budgets.list(), hasLength(2)); + expect(session.sessionKey, contains('demo')); + }); + + test('sign out clears the session', () async { + final storage = InMemorySessionStorage({ + 'token': 'jwt', + 'baseUrl': 'https://ppm.example.com', + 'user': '{"user_id":7}', + }); + final session = SessionController(config: AppConfig.demo, storage: storage); + await session.restore(); + + await session.signOut(); + + expect(session.status, SessionStatus.signedOut); + expect(await storage.readAll(), isEmpty); + }); + }); +} diff --git a/mobile/test/formatters_test.dart b/mobile/test/formatters_test.dart new file mode 100644 index 0000000..f96b722 --- /dev/null +++ b/mobile/test/formatters_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:please_pay_me/core/format/formatters.dart'; + +import 'support/test_setup.dart'; + +void main() { + setUpAll(setUpTestEnvironment); + + test('money uses Russian grouping and the ruble sign', () { + expect(formatMoney(1234.5), '1 234,50 ₽'); + expect(formatMoney(250, compact: true), '250 ₽'); + }); + + test('expenses are rendered as a deduction', () { + expect(formatSignedMoney(250), '−250,00 ₽'); + expect(formatSignedMoney(-250), '+250,00 ₽'); + }); + + test('plural follows Russian rules', () { + expect(plural(1, 'день', 'дня', 'дней'), '1 день'); + expect(plural(3, 'день', 'дня', 'дней'), '3 дня'); + expect(plural(11, 'день', 'дня', 'дней'), '11 дней'); + expect(plural(21, 'день', 'дня', 'дней'), '21 день'); + }); + + test('relative day switches to a date beyond yesterday', () { + final now = DateTime(2026, 9, 19); + + expect(formatRelativeDay(now, now: now), 'Сегодня'); + expect(formatRelativeDay(DateTime(2026, 9, 18), now: now), 'Вчера'); + expect(formatRelativeDay(DateTime(2026, 9, 12), now: now), '12 сентября'); + }); + + test('days left is humanized', () { + expect(formatDaysLeft(0), 'период завершён'); + expect(formatDaysLeft(5), 'осталось 5 дней'); + }); +} diff --git a/mobile/test/legal_links_test.dart b/mobile/test/legal_links_test.dart new file mode 100644 index 0000000..9f677bf --- /dev/null +++ b/mobile/test/legal_links_test.dart @@ -0,0 +1,19 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:please_pay_me/core/config/app_config.dart'; +import 'package:please_pay_me/core/legal/legal_links.dart'; + +void main() { + test('legal URLs point at the cabinet origin', () { + expect( + LegalLinks.resolve('https://please-pay-me.ru/', LegalLinks.privacy), + Uri.parse('https://please-pay-me.ru/legal/privacy'), + ); + }); + + test('empty cabinet falls back to production', () { + expect( + LegalLinks.resolve('', LegalLinks.offer), + Uri.parse('${AppConfig.productionOrigin}/legal/offer'), + ); + }); +} diff --git a/mobile/test/models_test.dart b/mobile/test/models_test.dart new file mode 100644 index 0000000..7cb54dc --- /dev/null +++ b/mobile/test/models_test.dart @@ -0,0 +1,122 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:please_pay_me/data/models/budget.dart'; +import 'package:please_pay_me/data/models/expense.dart'; +import 'package:please_pay_me/data/models/job.dart'; +import 'package:please_pay_me/data/models/json.dart'; + +void main() { + group('json helpers', () { + test('coerce numbers coming as int, double or string', () { + expect(asDouble(10), 10.0); + expect(asDouble(10.5), 10.5); + expect(asDouble('10,5'), 10.5); + expect(asDouble(null, fallback: -1), -1); + expect(asInt('42'), 42); + }); + + test('asDate keeps the calendar day and drops time', () { + expect(asDate('2026-09-19'), DateTime(2026, 9, 19)); + expect(asDate('2026-09-19T23:45:00Z'), DateTime(2026, 9, 19)); + }); + + test('formatIsoDate pads month and day', () { + expect(formatIsoDate(DateTime(2026, 1, 5)), '2026-01-05'); + }); + }); + + group('BudgetStatus', () { + BudgetStatus parse(Map overrides) { + return BudgetStatus.fromJson({ + 'budget': { + 'id': 1, + 'user_id': 7, + 'name': 'До аванса', + 'total_amount': 42000, + 'start_date': '2026-09-13', + 'end_date': '2026-09-27', + 'currency': 'RUB', + 'is_active': true, + }, + 'today': '2026-09-19', + 'days_left': 9, + 'total_spent': 21000, + 'remaining': 21000, + 'daily_limit': 2333.33, + 'spent_today': 1200, + 'remaining_today': 1133.33, + 'is_over_daily': false, + 'is_over_budget': false, + 'is_expired': false, + 'selected': true, + ...overrides, + }); + } + + test('parses nested budget', () { + final status = parse({}); + + expect(status.budget.name, 'До аванса'); + expect(status.budget.endDate, DateTime(2026, 9, 27)); + expect(status.selected, isTrue); + }); + + test('spentProgress is a clamped share of the total', () { + expect(parse({}).spentProgress, closeTo(0.5, 0.001)); + expect(parse({'total_spent': 50000}).spentProgress, 1.0); + }); + + test('dailyProgress falls back to full bar when the limit is zero', () { + expect(parse({'daily_limit': 0, 'spent_today': 500}).dailyProgress, 1.0); + expect(parse({'daily_limit': 0, 'spent_today': 0}).dailyProgress, 0.0); + }); + }); + + group('ExpensesPage', () { + test('reports pagination state', () { + final page = ExpensesPage.fromJson({ + 'page': 1, + 'total_pages': 3, + 'total_count': 55, + 'page_size': 20, + 'total_sum': 12345.5, + 'budget_id': 4, + 'items': [ + {'id': 1, 'budget_id': 4, 'amount': 250, 'note': 'Кофе', 'spent_at': '2026-09-19'}, + {'id': 2, 'budget_id': 4, 'amount': 100.5, 'note': null, 'spent_at': '2026-09-18'}, + ], + }); + + expect(page.hasMore, isTrue); + expect(page.items, hasLength(2)); + expect(page.items.last.note, isNull); + expect(page.totalSum, 12345.5); + }); + }); + + group('Job', () { + test('parses pay days and the weekend policy', () { + final job = Job.fromJson({ + 'id': 1, + 'user_id': 7, + 'name': 'Основная', + 'salary_amount': 180000, + 'currency': 'RUB', + 'pay_days': [5, 20], + 'first_pay_percent': 40, + 'weekend_policy': 'after_weekend', + 'is_active': true, + 'next_pays': [ + {'date': '2026-09-20', 'scheduled_day': 20, 'percent': 60, 'amount': 108000}, + ], + }); + + expect(job.payDays, [5, 20]); + expect(job.weekendPolicy, WeekendPolicy.afterWeekend); + expect(job.nextPay?.amount, 108000); + }); + + test('unknown weekend policy degrades to the default', () { + expect(WeekendPolicy.fromWire('nonsense'), WeekendPolicy.beforeWeekend); + }); + }); +} diff --git a/mobile/test/support/test_setup.dart b/mobile/test/support/test_setup.dart new file mode 100644 index 0000000..830d707 --- /dev/null +++ b/mobile/test/support/test_setup.dart @@ -0,0 +1,10 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +/// Shared bootstrap: no font downloads, Russian date symbols available. +Future setUpTestEnvironment() async { + TestWidgetsFlutterBinding.ensureInitialized(); + GoogleFonts.config.allowRuntimeFetching = false; + await initializeDateFormatting('ru'); +} diff --git a/mobile/test/theme_controller_test.dart b/mobile/test/theme_controller_test.dart new file mode 100644 index 0000000..88bce21 --- /dev/null +++ b/mobile/test/theme_controller_test.dart @@ -0,0 +1,44 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:please_pay_me/theme/theme.dart'; + +void main() { + group('ThemePreference', () { + test('parse falls back to system', () { + expect(ThemePreference.parse(null), ThemePreference.system); + expect(ThemePreference.parse('nope'), ThemePreference.system); + expect(ThemePreference.parse('light'), ThemePreference.light); + expect(ThemePreference.parse('dark'), ThemePreference.dark); + }); + + test('resolve follows the platform only in system mode', () { + expect(ThemePreference.system.resolve(Brightness.dark), Brightness.dark); + expect(ThemePreference.light.resolve(Brightness.dark), Brightness.light); + expect(ThemePreference.dark.resolve(Brightness.light), Brightness.dark); + }); + }); + + group('systemUiOverlayFor', () { + test('dark paints the Android nav bar transparent black without a scrim', () { + final overlay = systemUiOverlayFor(Brightness.dark); + expect(overlay.systemNavigationBarColor, const Color(0x00000000)); + expect(overlay.systemNavigationBarContrastEnforced, isFalse); + expect(overlay.systemNavigationBarIconBrightness, Brightness.light); + }); + }); + + group('ThemeController', () { + test('restore and setPreference persist independently of session', () async { + final store = MemoryThemeStore('dark'); + final controller = ThemeController(store: store); + + await controller.restore(); + expect(controller.preference, ThemePreference.dark); + + await controller.setPreference(ThemePreference.light); + expect(controller.preference, ThemePreference.light); + expect(store.value, 'light'); + expect(controller.resolve(Brightness.dark), Brightness.light); + }); + }); +} diff --git a/mobile/test/widget_test.dart b/mobile/test/widget_test.dart new file mode 100644 index 0000000..b39afa6 --- /dev/null +++ b/mobile/test/widget_test.dart @@ -0,0 +1,139 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:please_pay_me/app/app.dart'; +import 'package:please_pay_me/core/config/app_config.dart'; +import 'package:please_pay_me/core/storage/session_storage.dart'; +import 'package:please_pay_me/data/demo/demo_backend.dart'; +import 'package:please_pay_me/features/auth/session_controller.dart'; +import 'package:please_pay_me/theme/theme.dart'; + +import 'support/test_setup.dart'; + +void main() { + setUpAll(setUpTestEnvironment); + + SessionController demoSession({bool seeded = true}) { + return SessionController( + config: AppConfig.demo, + storage: InMemorySessionStorage(), + demoBackend: seeded ? DemoBackend() : DemoBackend.empty(), + ); + } + + /// Skeletons pulse forever, so `pumpAndSettle` would time out. + Future settle(WidgetTester tester) async { + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + } + + testWidgets('restoring session shows the branded splash', (tester) async { + final session = demoSession(); + + await tester.pumpWidget(PleasePayMeApp(session: session)); + await tester.pump(); + + expect(find.text('Дожить до ЗП'), findsOneWidget); + expect(find.text('Бюджет от зарплаты до зарплаты'), findsOneWidget); + expect(find.byType(CupertinoActivityIndicator), findsOneWidget); + }); + + testWidgets('signed-out app shows the login screen', (tester) async { + final session = demoSession(); + await session.restore(); + + await tester.pumpWidget(PleasePayMeApp(session: session)); + await settle(tester); + + expect(find.text('Дожить до ЗП'), findsOneWidget); + expect(find.text('Войти через Яндекс'), findsOneWidget); + }); + + testWidgets('demo session opens the overview with the current envelope', + (tester) async { + final session = demoSession()..startDemo(); + + await tester.pumpWidget(PleasePayMeApp(session: session)); + await settle(tester); + + expect(find.text('Обзор'), findsWidgets); + expect(find.text('До аванса'), findsOneWidget); + expect(find.text('Остаток бюджета'), findsOneWidget); + + await tester.drag(find.byType(CustomScrollView).first, const Offset(0, -500)); + await settle(tester); + + // Grouped headers are uppercased by `AppSectionHeader`, as in iOS Settings. + expect(find.text('БЛИЖАЙШАЯ ВЫПЛАТА'), findsOneWidget); + expect(tester.takeException(), isNull); + }); + + testWidgets('tabs switch between the main sections', (tester) async { + final session = demoSession()..startDemo(); + + await tester.pumpWidget(PleasePayMeApp(session: session)); + await settle(tester); + + for (final tab in ['Журнал', 'Бюджеты', 'Работа', 'Профиль']) { + await tester.tap(find.text(tab).last); + await settle(tester); + expect(tester.takeException(), isNull, reason: 'tab $tab failed to render'); + } + + expect(find.text('Демо'), findsOneWidget); + }); + + testWidgets('profile theme switcher applies dark appearance', (tester) async { + final session = demoSession()..startDemo(); + final theme = ThemeController(store: MemoryThemeStore()); + + await tester.pumpWidget(PleasePayMeApp(session: session, theme: theme)); + await settle(tester); + + await tester.tap(find.text('Профиль').last); + await settle(tester); + + expect(find.text('Системная'), findsOneWidget); + expect(find.text('Светлая'), findsOneWidget); + + await tester.tap(find.text('Тёмная')); + await settle(tester); + + expect(theme.preference, ThemePreference.dark); + expect(theme.resolve(Brightness.light), Brightness.dark); + }); + + testWidgets('empty demo backend renders the empty states', (tester) async { + final session = demoSession(seeded: false)..startDemo(); + + await tester.pumpWidget(PleasePayMeApp(session: session)); + await settle(tester); + + expect(find.text('Бюджета пока нет'), findsOneWidget); + expect(find.text('Создать бюджет'), findsOneWidget); + }); + + testWidgets('recording an expense updates the remaining amount', (tester) async { + final session = demoSession()..startDemo(); + + await tester.pumpWidget(PleasePayMeApp(session: session)); + await settle(tester); + + await tester.tap(find.text('Добавить трату')); + await settle(tester); + expect(find.text('Новая трата'), findsOneWidget); + + await tester.enterText(find.byType(CupertinoTextField).first, '1000'); + await settle(tester); + + await tester.tap(find.text('Записать трату')); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + + expect(find.text('Новая трата'), findsNothing); + expect(find.text('Трата записана'), findsOneWidget); + + // Let the toast auto-dismiss so no timers outlive the test. + await tester.pump(const Duration(seconds: 3)); + expect(tester.takeException(), isNull); + }); +} diff --git a/mobile/web/favicon.png b/mobile/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/mobile/web/favicon.png differ diff --git a/mobile/web/icons/Icon-192.png b/mobile/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/mobile/web/icons/Icon-192.png differ diff --git a/mobile/web/icons/Icon-512.png b/mobile/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/mobile/web/icons/Icon-512.png differ diff --git a/mobile/web/icons/Icon-maskable-192.png b/mobile/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/mobile/web/icons/Icon-maskable-192.png differ diff --git a/mobile/web/icons/Icon-maskable-512.png b/mobile/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/mobile/web/icons/Icon-maskable-512.png differ diff --git a/mobile/web/index.html b/mobile/web/index.html new file mode 100644 index 0000000..ec71cdd --- /dev/null +++ b/mobile/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + please_pay_me + + + + + + + diff --git a/mobile/web/manifest.json b/mobile/web/manifest.json new file mode 100644 index 0000000..10de72f --- /dev/null +++ b/mobile/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "please_pay_me", + "short_name": "please_pay_me", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/mobile/widgetbook/.gitignore b/mobile/widgetbook/.gitignore new file mode 100644 index 0000000..79f7eca --- /dev/null +++ b/mobile/widgetbook/.gitignore @@ -0,0 +1,48 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Widget Preview related +.widget_preview/ diff --git a/mobile/widgetbook/.metadata b/mobile/widgetbook/.metadata new file mode 100644 index 0000000..a7644bb --- /dev/null +++ b/mobile/widgetbook/.metadata @@ -0,0 +1,39 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "6a19cca56475dbfba1478ee68d7bd0c2ef891da1" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + base_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + - platform: android + create_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + base_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + - platform: ios + create_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + base_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + - platform: web + create_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + base_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + - platform: windows + create_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + base_revision: 6a19cca56475dbfba1478ee68d7bd0c2ef891da1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/mobile/widgetbook/README.md b/mobile/widgetbook/README.md new file mode 100644 index 0000000..4147cdb --- /dev/null +++ b/mobile/widgetbook/README.md @@ -0,0 +1,96 @@ +# Please Pay Me — Widgetbook + +Portable component catalog for the mobile UI kit. Lives as a **separate package** +under `mobile/widgetbook/` and depends on the app package via path — no changes +required in production `lib/main.dart`. + +## Run + +```powershell +cd mobile\widgetbook +.\run.ps1 # chrome по умолчанию +.\run.ps1 -Device windows +``` + +Или из `mobile/`: + +```powershell +.\run.ps1 -Target widgetbook -Device chrome +``` + +```bash +cd mobile/widgetbook +flutter pub get +flutter run -d chrome +# or +flutter run -d windows +``` + +## Structure + +``` +widgetbook/ +├── lib/ +│ ├── main.dart +│ ├── app.dart # WidgetbookRoot + addons +│ ├── catalog.dart # directory tree (also used by smoke tests) +│ ├── addons/ # theme / locale / device frame +│ ├── knobs/ # shared knob helpers +│ ├── support/preview.dart # iOS grouped-background canvas for use-cases +│ ├── support/demo_scope.dart # real screens on the in-memory DemoBackend +│ └── use_cases/ # Atoms → Molecules → Navigation → Feedback → Screens +└── test/catalog_smoke_test.dart +``` + +## iOS kit + +Компоненты построены на Cupertino и следуют Apple HIG: + +- семантические цвета (`label` / `separator` / `groupedBackground`) объявлены как + `CupertinoDynamicColor`, поэтому light/dark резолвится автоматически; +- типографика — шкала SF Pro (`largeTitle` 34 … `caption2` 11), в качестве + кросс-платформенной замены SF используется Inter; +- списки — inset-grouped секции с hairline-разделителями (0.5pt) и отступом + под leading-бейдж, как в Settings; +- контролы — `CupertinoButton`, `CupertinoTextField`, `CupertinoSwitch`, + `CupertinoSlidingSegmentedControl`, `CupertinoTabBar`, `CupertinoAlertDialog`, + `CupertinoActionSheet`, `CupertinoActivityIndicator`; +- вместо Material SnackBar — `AppToast`: плавающая blur-капсула поверх контента. + +Бренд-тинт остался зелёным (`AppColors.accent`) и подменяет `systemBlue`. + +Use-cases render **real widgets** from `package:please_pay_me/ui/...` and themes from +`package:please_pay_me/theme/...`. + +Addons: + +- **Theme** — Light / Dark (`CupertinoThemeAddon`, `buildLightTheme` / `buildDarkTheme`) +- **Locale** — `ru` / `en` +- **Viewport** — iPhone / Android frames + +## Reuse in another Flutter project + +1. Copy `widgetbook/` folder (or this package). +2. Point `please_pay_me` path dependency to your UI package (or rename dependency). +3. Keep exporting widgets from that package; use-cases only import them. +4. Run `flutter pub get && flutter run`. + +Optional codegen (annotations already in `pubspec.yaml`): + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +Current catalog is **manual** via `catalog.dart` so it stays easy to copy without +a generator step. + +## Tests + +```bash +cd mobile/widgetbook +flutter test +``` + +Smoke-тесты проверяют, что структура каталога на месте, и **рендерят каждый use-case +в светлой и тёмной теме** внутри `WidgetbookScope` (knobs читают состояние из контекста), +падая на любом исключении при построении. diff --git a/mobile/widgetbook/analysis_options.yaml b/mobile/widgetbook/analysis_options.yaml new file mode 100644 index 0000000..57a08d4 --- /dev/null +++ b/mobile/widgetbook/analysis_options.yaml @@ -0,0 +1,12 @@ +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** +include: package:flutter_lints/flutter.yaml + +linter: + rules: + prefer_const_constructors: false diff --git a/mobile/widgetbook/android/.gitignore b/mobile/widgetbook/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/mobile/widgetbook/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/mobile/widgetbook/android/app/build.gradle.kts b/mobile/widgetbook/android/app/build.gradle.kts new file mode 100644 index 0000000..eec042b --- /dev/null +++ b/mobile/widgetbook/android/app/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.please_pay_me_widgetbook" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.please_pay_me_widgetbook" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + // Uses the version code from pubspec.yaml. When using split APKs, 1000 * ABI_VERSION + // is added automatically by Flutter. (https://developer.android.com/studio/build/configure-apk-splits#configure-APK-versions) + // You can force using the value of versionCode by specifying the `-P force-version-code-ignoring-abi=true` + // flag during build. + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/mobile/widgetbook/android/app/src/debug/AndroidManifest.xml b/mobile/widgetbook/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/mobile/widgetbook/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile/widgetbook/android/app/src/main/AndroidManifest.xml b/mobile/widgetbook/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..48753ab --- /dev/null +++ b/mobile/widgetbook/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/widgetbook/android/app/src/main/kotlin/com/example/please_pay_me_widgetbook/MainActivity.kt b/mobile/widgetbook/android/app/src/main/kotlin/com/example/please_pay_me_widgetbook/MainActivity.kt new file mode 100644 index 0000000..d5a40a1 --- /dev/null +++ b/mobile/widgetbook/android/app/src/main/kotlin/com/example/please_pay_me_widgetbook/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.please_pay_me_widgetbook + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/mobile/widgetbook/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile/widgetbook/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/mobile/widgetbook/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile/widgetbook/android/app/src/main/res/drawable/launch_background.xml b/mobile/widgetbook/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/mobile/widgetbook/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile/widgetbook/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/widgetbook/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/mobile/widgetbook/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/mobile/widgetbook/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/widgetbook/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/mobile/widgetbook/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/mobile/widgetbook/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/widgetbook/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/mobile/widgetbook/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/mobile/widgetbook/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/widgetbook/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/mobile/widgetbook/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/mobile/widgetbook/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/widgetbook/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/mobile/widgetbook/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/mobile/widgetbook/android/app/src/main/res/values-night/styles.xml b/mobile/widgetbook/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/mobile/widgetbook/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/widgetbook/android/app/src/main/res/values/styles.xml b/mobile/widgetbook/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/mobile/widgetbook/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/widgetbook/android/app/src/profile/AndroidManifest.xml b/mobile/widgetbook/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/mobile/widgetbook/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile/widgetbook/android/build.gradle.kts b/mobile/widgetbook/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/mobile/widgetbook/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/mobile/widgetbook/android/gradle.properties b/mobile/widgetbook/android/gradle.properties new file mode 100644 index 0000000..e96108c --- /dev/null +++ b/mobile/widgetbook/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/mobile/widgetbook/android/gradle/wrapper/gradle-wrapper.properties b/mobile/widgetbook/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a20f2c4 --- /dev/null +++ b/mobile/widgetbook/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip diff --git a/mobile/widgetbook/android/settings.gradle.kts b/mobile/widgetbook/android/settings.gradle.kts new file mode 100644 index 0000000..b28021a --- /dev/null +++ b/mobile/widgetbook/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.1.0" apply false + id("org.jetbrains.kotlin.android") version "2.4.0" apply false +} + +include(":app") diff --git a/mobile/widgetbook/ios/.gitignore b/mobile/widgetbook/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/mobile/widgetbook/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/mobile/widgetbook/ios/Flutter/AppFrameworkInfo.plist b/mobile/widgetbook/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..391a902 --- /dev/null +++ b/mobile/widgetbook/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/mobile/widgetbook/ios/Flutter/Debug.xcconfig b/mobile/widgetbook/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/mobile/widgetbook/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/mobile/widgetbook/ios/Flutter/Release.xcconfig b/mobile/widgetbook/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/mobile/widgetbook/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/mobile/widgetbook/ios/Runner.xcodeproj/project.pbxproj b/mobile/widgetbook/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e018ee9 --- /dev/null +++ b/mobile/widgetbook/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,647 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.pleasePayMeWidgetbook; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pleasePayMeWidgetbook.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pleasePayMeWidgetbook.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pleasePayMeWidgetbook.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.pleasePayMeWidgetbook; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.pleasePayMeWidgetbook; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/mobile/widgetbook/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mobile/widgetbook/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/mobile/widgetbook/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile/widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/mobile/widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/mobile/widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile/widgetbook/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/widgetbook/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c3fedb2 --- /dev/null +++ b/mobile/widgetbook/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/widgetbook/ios/Runner.xcworkspace/contents.xcworkspacedata b/mobile/widgetbook/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/mobile/widgetbook/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile/widgetbook/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/widgetbook/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/mobile/widgetbook/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/widgetbook/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/widgetbook/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/mobile/widgetbook/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile/widgetbook/ios/Runner/AppDelegate.swift b/mobile/widgetbook/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/mobile/widgetbook/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/mobile/widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/mobile/widgetbook/ios/Runner/Base.lproj/LaunchScreen.storyboard b/mobile/widgetbook/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/mobile/widgetbook/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/widgetbook/ios/Runner/Base.lproj/Main.storyboard b/mobile/widgetbook/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/mobile/widgetbook/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/widgetbook/ios/Runner/Info.plist b/mobile/widgetbook/ios/Runner/Info.plist new file mode 100644 index 0000000..465df30 --- /dev/null +++ b/mobile/widgetbook/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Please Pay Me Widgetbook + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + please_pay_me_widgetbook + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/mobile/widgetbook/ios/Runner/Runner-Bridging-Header.h b/mobile/widgetbook/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/mobile/widgetbook/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/mobile/widgetbook/ios/Runner/SceneDelegate.swift b/mobile/widgetbook/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/mobile/widgetbook/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/mobile/widgetbook/ios/RunnerTests/RunnerTests.swift b/mobile/widgetbook/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/mobile/widgetbook/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/mobile/widgetbook/lib/addons/device_frame_addon.dart b/mobile/widgetbook/lib/addons/device_frame_addon.dart new file mode 100644 index 0000000..3ddd8a5 --- /dev/null +++ b/mobile/widgetbook/lib/addons/device_frame_addon.dart @@ -0,0 +1,11 @@ +import 'package:widgetbook/widgetbook.dart'; + +/// Common phone frames for visual QA (ViewportAddon replaces DeviceFrameAddon). +ViewportAddon buildDeviceFrameAddon() { + return ViewportAddon([ + Viewports.none, + IosViewports.iPhone13, + IosViewports.iPhone13Mini, + AndroidViewports.samsungGalaxyS20, + ]); +} diff --git a/mobile/widgetbook/lib/addons/locale_addon.dart b/mobile/widgetbook/lib/addons/locale_addon.dart new file mode 100644 index 0000000..75ae0f9 --- /dev/null +++ b/mobile/widgetbook/lib/addons/locale_addon.dart @@ -0,0 +1,19 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:widgetbook/widgetbook.dart'; + +/// Locale switcher: Russian / English. +LocalizationAddon buildLocaleAddon() { + return LocalizationAddon( + locales: const [ + Locale('ru'), + Locale('en'), + ], + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + initialLocale: const Locale('ru'), + ); +} diff --git a/mobile/widgetbook/lib/addons/theme_addon.dart b/mobile/widgetbook/lib/addons/theme_addon.dart new file mode 100644 index 0000000..752d834 --- /dev/null +++ b/mobile/widgetbook/lib/addons/theme_addon.dart @@ -0,0 +1,12 @@ +import 'package:please_pay_me/theme/theme.dart'; +import 'package:widgetbook/widgetbook.dart'; + +/// Light / dark Cupertino theme built from the shared product tokens. +CupertinoThemeAddon buildThemeAddon() { + return CupertinoThemeAddon( + themes: [ + WidgetbookTheme(name: 'Light', data: buildLightTheme()), + WidgetbookTheme(name: 'Dark', data: buildDarkTheme()), + ], + ); +} diff --git a/mobile/widgetbook/lib/app.dart b/mobile/widgetbook/lib/app.dart new file mode 100644 index 0000000..c7077c9 --- /dev/null +++ b/mobile/widgetbook/lib/app.dart @@ -0,0 +1,26 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me_widgetbook/addons/device_frame_addon.dart'; +import 'package:please_pay_me_widgetbook/addons/locale_addon.dart'; +import 'package:please_pay_me_widgetbook/addons/theme_addon.dart'; +import 'package:please_pay_me_widgetbook/catalog.dart'; +import 'package:widgetbook/widgetbook.dart'; + +/// Entry Widgetbook application — portable across projects that depend on the +/// same UI package (`please_pay_me`). +class WidgetbookRoot extends StatelessWidget { + const WidgetbookRoot({super.key}); + + @override + Widget build(BuildContext context) { + return Widgetbook.cupertino( + directories: buildCatalogDirectories(), + addons: [ + buildThemeAddon(), + buildLocaleAddon(), + buildDeviceFrameAddon(), + InspectorAddon(), + AlignmentAddon(initialAlignment: Alignment.center), + ], + ); + } +} diff --git a/mobile/widgetbook/lib/catalog.dart b/mobile/widgetbook/lib/catalog.dart new file mode 100644 index 0000000..b0ab3d2 --- /dev/null +++ b/mobile/widgetbook/lib/catalog.dart @@ -0,0 +1,75 @@ +import 'package:please_pay_me_widgetbook/use_cases/atoms/buttons_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/atoms/icons_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/atoms/text_fields_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/atoms/typography_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/feedback/progress_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/feedback/skeletons_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/feedback/toasts_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/molecules/avatars_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/molecules/cards_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/molecules/chips_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/molecules/list_tiles_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/molecules/switches_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/navigation/dialogs_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/navigation/nav_bar_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/navigation/segmented_control_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/navigation/tab_bar_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/screens/app_screens_use_cases.dart'; +import 'package:please_pay_me_widgetbook/use_cases/screens/sheets_use_cases.dart'; +import 'package:widgetbook/widgetbook.dart'; + +/// Single source of directory tree for Widgetbook + smoke tests. +List buildCatalogDirectories() { + return [ + WidgetbookFolder( + name: 'Atoms', + children: [ + buttonsComponent(), + textFieldsComponent(), + typographyComponent(), + iconsComponent(), + ], + ), + WidgetbookFolder( + name: 'Molecules', + children: [ + cardsComponent(), + listTilesComponent(), + switchesComponent(), + chipsComponent(), + avatarsComponent(), + ], + ), + WidgetbookFolder( + name: 'Navigation', + children: [ + navBarComponent(), + tabBarComponent(), + segmentedControlComponent(), + dialogsComponent(), + ], + ), + WidgetbookFolder( + name: 'Feedback', + children: [ + progressComponent(), + skeletonsComponent(), + toastsComponent(), + ], + ), + WidgetbookFolder( + name: 'Screens', + children: [ + overviewScreenComponent(), + journalScreenComponent(), + budgetsScreenComponent(), + workScreenComponent(), + profileScreenComponent(), + splashScreenComponent(), + loginScreenComponent(), + sheetsComponent(), + homeTabsComponent(), + ], + ), + ]; +} diff --git a/mobile/widgetbook/lib/knobs/common_knobs.dart b/mobile/widgetbook/lib/knobs/common_knobs.dart new file mode 100644 index 0000000..3cba30b --- /dev/null +++ b/mobile/widgetbook/lib/knobs/common_knobs.dart @@ -0,0 +1,93 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:widgetbook/widgetbook.dart'; + +String knobText( + BuildContext context, { + required String label, + String initialValue = 'Label', +}) { + return context.knobs.string(label: label, initialValue: initialValue); +} + +bool knobBool( + BuildContext context, { + required String label, + bool initialValue = false, +}) { + return context.knobs.boolean(label: label, initialValue: initialValue); +} + +double knobDouble( + BuildContext context, { + required String label, + double initialValue = 0.5, + double min = 0, + double max = 1, +}) { + return context.knobs.double.slider( + label: label, + initialValue: initialValue, + min: min, + max: max, + ); +} + +int knobInt( + BuildContext context, { + required String label, + int initialValue = 0, + int min = 0, + int max = 10, +}) { + return context.knobs.int.slider( + label: label, + initialValue: initialValue, + min: min, + max: max, + ); +} + +Color knobColor( + BuildContext context, { + required String label, + Color? initialValue, +}) { + return context.knobs.color( + label: label, + initialValue: initialValue ?? AppColors.of(context, AppColors.accent), + ); +} + +/// Dropdown over iOS system tints used by badges and progress bars. +Color knobTint(BuildContext context, {String label = 'Tint'}) { + const options = { + 'accent': AppColors.accent, + 'red': AppColors.systemRed, + 'orange': AppColors.systemOrange, + 'green': AppColors.systemGreen, + 'gray': AppColors.systemGray, + }; + + final key = context.knobs.object.dropdown( + label: label, + options: options.keys.toList(), + initialOption: 'accent', + ); + + return options[key]!; +} + +T knobEnum( + BuildContext context, { + required String label, + required List values, + required T initial, +}) { + return context.knobs.object.dropdown( + label: label, + options: values, + labelBuilder: (value) => value.name, + initialOption: initial, + ); +} diff --git a/mobile/widgetbook/lib/knobs/state_knobs.dart b/mobile/widgetbook/lib/knobs/state_knobs.dart new file mode 100644 index 0000000..3a4f156 --- /dev/null +++ b/mobile/widgetbook/lib/knobs/state_knobs.dart @@ -0,0 +1,46 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:widgetbook/widgetbook.dart'; + +enum UiAsyncState { content, loading, empty, error } + +UiAsyncState knobAsyncState(BuildContext context, {String label = 'State'}) { + return context.knobs.object.dropdown( + label: label, + options: UiAsyncState.values, + labelBuilder: (value) => value.name, + initialOption: UiAsyncState.content, + ); +} + +/// Shared empty / error / loading wrappers for molecules & screens. +Widget wrapAsyncState({ + required BuildContext context, + required UiAsyncState state, + required Widget Function() builder, + String emptyLabel = 'Пока пусто', + String errorLabel = 'Что-то пошло не так', +}) { + return switch (state) { + UiAsyncState.loading => const Padding( + padding: EdgeInsets.all(AppSpacing.s6), + child: Center(child: AppSpinner()), + ), + UiAsyncState.empty => Padding( + padding: const EdgeInsets.all(AppSpacing.s6), + child: Center(child: AppText.subhead(emptyLabel)), + ), + UiAsyncState.error => Padding( + padding: const EdgeInsets.all(AppSpacing.s6), + child: Center( + child: AppText.subhead( + knobText(context, label: 'Error text', initialValue: errorLabel), + color: AppColors.systemRed, + ), + ), + ), + UiAsyncState.content => builder(), + }; +} diff --git a/mobile/widgetbook/lib/main.dart b/mobile/widgetbook/lib/main.dart new file mode 100644 index 0000000..43d4ccc --- /dev/null +++ b/mobile/widgetbook/lib/main.dart @@ -0,0 +1,10 @@ +import 'package:flutter/widgets.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:please_pay_me_widgetbook/app.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await initializeDateFormatting('ru'); + + runApp(const WidgetbookRoot()); +} diff --git a/mobile/widgetbook/lib/support/demo_scope.dart b/mobile/widgetbook/lib/support/demo_scope.dart new file mode 100644 index 0000000..790bc0a --- /dev/null +++ b/mobile/widgetbook/lib/support/demo_scope.dart @@ -0,0 +1,76 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/core/config/app_config.dart'; +import 'package:please_pay_me/core/storage/session_storage.dart'; +import 'package:please_pay_me/data/demo/demo_backend.dart'; +import 'package:please_pay_me/features/auth/session_controller.dart'; +import 'package:please_pay_me/features/budgets/budgets_controller.dart'; +import 'package:please_pay_me/features/journal/journal_controller.dart'; +import 'package:please_pay_me/features/work/jobs_controller.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:provider/provider.dart'; + +/// Wires real app screens to the in-memory [DemoBackend] so the catalog shows +/// production widgets with production state management. +class DemoScope extends StatefulWidget { + const DemoScope({super.key, required this.child, this.seeded = true}); + + final Widget child; + + /// `false` renders the empty states (no budgets / jobs / operations). + final bool seeded; + + @override + State createState() => _DemoScopeState(); +} + +class _DemoScopeState extends State { + late DemoBackend _backend; + late SessionController _session; + + @override + void initState() { + super.initState(); + _build(); + } + + @override + void didUpdateWidget(DemoScope oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.seeded != widget.seeded) _build(); + } + + void _build() { + _backend = widget.seeded ? DemoBackend() : DemoBackend.empty(); + _session = SessionController( + config: AppConfig.demo, + storage: InMemorySessionStorage(), + demoBackend: _backend, + )..startDemo(); + } + + @override + Widget build(BuildContext context) { + return MultiProvider( + key: ValueKey(widget.seeded), + providers: [ + ChangeNotifierProvider.value(value: _session), + ChangeNotifierProvider( + create: (_) => ThemeController(store: MemoryThemeStore()), + ), + ChangeNotifierProvider( + create: (_) => BudgetsController( + budgets: _backend.budgets, + expenses: _backend.expenses, + )..load(), + ), + ChangeNotifierProvider( + create: (_) => JournalController(expenses: _backend.expenses)..load(), + ), + ChangeNotifierProvider( + create: (_) => JobsController(jobs: _backend.jobs)..load(), + ), + ], + child: widget.child, + ); + } +} diff --git a/mobile/widgetbook/lib/support/preview.dart b/mobile/widgetbook/lib/support/preview.dart new file mode 100644 index 0000000..74e2039 --- /dev/null +++ b/mobile/widgetbook/lib/support/preview.dart @@ -0,0 +1,56 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; + +/// Puts a use-case on the iOS grouped background so inset-grouped cards and +/// separators read the way they do inside the app. +class IosPreview extends StatelessWidget { + const IosPreview({ + super.key, + required this.child, + this.padding = const EdgeInsets.symmetric(vertical: AppSpacing.s5), + this.maxWidth = 420, + this.stretch = false, + }); + + /// Preview for controls that should not be stretched (buttons, chips). + const IosPreview.centered({super.key, required this.child}) + : padding = const EdgeInsets.all(AppSpacing.s5), + maxWidth = 420, + stretch = false; + + final Widget child; + final EdgeInsetsGeometry padding; + final double maxWidth; + final bool stretch; + + @override + Widget build(BuildContext context) { + return ColoredBox( + color: AppColors.of(context, AppColors.groupedBackground), + child: Center( + child: SingleChildScrollView( + padding: padding, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: maxWidth), + child: Column( + crossAxisAlignment: + stretch ? CrossAxisAlignment.stretch : CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [child], + ), + ), + ), + ), + ); + } +} + +/// Full-screen preview (screens already bring their own scaffold). +class IosScreenPreview extends StatelessWidget { + const IosScreenPreview({super.key, required this.child}); + + final Widget child; + + @override + Widget build(BuildContext context) => child; +} diff --git a/mobile/widgetbook/lib/use_cases/atoms/buttons_use_cases.dart b/mobile/widgetbook/lib/use_cases/atoms/buttons_use_cases.dart new file mode 100644 index 0000000..ae9b58b --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/atoms/buttons_use_cases.dart @@ -0,0 +1,80 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent buttonsComponent() { + return WidgetbookComponent( + name: 'AppButton', + useCases: [ + WidgetbookUseCase( + name: 'Playground', + builder: (context) { + return IosPreview( + stretch: true, + child: AppButton( + label: knobText(context, label: 'Label', initialValue: 'Сохранить'), + style: knobEnum( + context, + label: 'Style', + values: AppButtonStyle.values, + initial: AppButtonStyle.filled, + ), + size: knobEnum( + context, + label: 'Size', + values: AppButtonSize.values, + initial: AppButtonSize.large, + ), + expanded: knobBool(context, label: 'Expanded', initialValue: true), + loading: knobBool(context, label: 'Loading'), + icon: knobBool(context, label: 'Icon') ? CupertinoIcons.plus : null, + onPressed: + knobBool(context, label: 'Enabled', initialValue: true) ? () {} : null, + ), + ); + }, + ), + WidgetbookUseCase( + name: 'All styles', + builder: (context) { + return IosPreview( + stretch: true, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (final style in AppButtonStyle.values) ...[ + AppButton(label: style.name, style: style, onPressed: () {}), + const SizedBox(height: AppSpacing.s3), + ], + ], + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Sizes', + builder: (context) { + return IosPreview.centered( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final size in AppButtonSize.values) ...[ + AppButton( + label: 'Размер ${size.name}', + size: size, + expanded: false, + onPressed: () {}, + ), + const SizedBox(height: AppSpacing.s3), + ], + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/atoms/icons_use_cases.dart b/mobile/widgetbook/lib/use_cases/atoms/icons_use_cases.dart new file mode 100644 index 0000000..c6adebf --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/atoms/icons_use_cases.dart @@ -0,0 +1,68 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +const _icons = [ + CupertinoIcons.house_fill, + CupertinoIcons.cart_fill, + CupertinoIcons.bag_fill, + CupertinoIcons.money_rubl_circle_fill, + CupertinoIcons.chart_pie_fill, + CupertinoIcons.calendar, + CupertinoIcons.bell_fill, + CupertinoIcons.gear_alt_fill, + CupertinoIcons.person_fill, + CupertinoIcons.creditcard_fill, +]; + +WidgetbookComponent iconsComponent() { + return WidgetbookComponent( + name: 'Icons', + useCases: [ + WidgetbookUseCase( + name: 'Glyphs', + builder: (context) { + final size = knobDouble(context, label: 'Size', initialValue: 24, min: 12, max: 48); + final tint = knobTint(context); + return IosPreview( + child: Wrap( + spacing: AppSpacing.s5, + runSpacing: AppSpacing.s5, + alignment: WrapAlignment.center, + children: [ + for (final icon in _icons) AppIcon(icon, size: size, color: tint), + ], + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Badges', + builder: (context) { + return IosPreview( + child: Wrap( + spacing: AppSpacing.s4, + runSpacing: AppSpacing.s4, + alignment: WrapAlignment.center, + children: [ + for (final (index, icon) in _icons.indexed) + AppIconBadge( + icon: icon, + color: switch (index % 4) { + 0 => AppColors.accent, + 1 => AppColors.systemRed, + 2 => AppColors.systemOrange, + _ => AppColors.systemGray, + }, + ), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/atoms/text_fields_use_cases.dart b/mobile/widgetbook/lib/use_cases/atoms/text_fields_use_cases.dart new file mode 100644 index 0000000..b8bf711 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/atoms/text_fields_use_cases.dart @@ -0,0 +1,62 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent textFieldsComponent() { + return WidgetbookComponent( + name: 'AppTextField', + useCases: [ + WidgetbookUseCase( + name: 'Playground', + builder: (context) { + final error = knobText(context, label: 'Error', initialValue: ''); + return IosPreview( + stretch: true, + padding: const EdgeInsets.all(AppSpacing.s4), + child: AppTextField( + label: knobText(context, label: 'Label', initialValue: 'Сумма'), + placeholder: knobText(context, label: 'Placeholder', initialValue: '0 ₽'), + enabled: knobBool(context, label: 'Enabled', initialValue: true), + obscureText: knobBool(context, label: 'Obscure'), + clearable: knobBool(context, label: 'Clear button', initialValue: true), + prefixIcon: knobBool(context, label: 'Prefix icon') + ? CupertinoIcons.search + : null, + errorText: error.isEmpty ? null : error, + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Form group', + builder: (context) { + return const IosPreview( + stretch: true, + padding: EdgeInsets.all(AppSpacing.s4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AppTextField(label: 'Название', placeholder: 'Продукты'), + SizedBox(height: AppSpacing.s4), + AppTextField( + label: 'Сумма', + placeholder: '0 ₽', + keyboardType: TextInputType.number, + ), + SizedBox(height: AppSpacing.s4), + AppTextField( + label: 'Комментарий', + placeholder: 'Необязательно', + errorText: 'Слишком длинный комментарий', + ), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/atoms/typography_use_cases.dart b/mobile/widgetbook/lib/use_cases/atoms/typography_use_cases.dart new file mode 100644 index 0000000..fbd13c6 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/atoms/typography_use_cases.dart @@ -0,0 +1,63 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent typographyComponent() { + return WidgetbookComponent( + name: 'Typography', + useCases: [ + WidgetbookUseCase( + name: 'iOS scale', + builder: (context) { + final sample = knobText(context, label: 'Sample', initialValue: 'Бюджет на неделю'); + return IosPreview( + stretch: true, + padding: const EdgeInsets.all(AppSpacing.s4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppText.largeTitle(sample), + const SizedBox(height: AppSpacing.s3), + AppText.title(sample), + const SizedBox(height: AppSpacing.s3), + AppText.headline(sample), + const SizedBox(height: AppSpacing.s3), + AppText.body(sample), + const SizedBox(height: AppSpacing.s3), + AppText.callout(sample), + const SizedBox(height: AppSpacing.s3), + AppText.subhead(sample), + const SizedBox(height: AppSpacing.s3), + AppText.footnote(sample), + const SizedBox(height: AppSpacing.s3), + AppText.caption(sample), + ], + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Section header', + builder: (context) { + return IosPreview( + stretch: true, + child: AppListSection( + header: knobText(context, label: 'Header', initialValue: 'Настройки'), + footer: knobText( + context, + label: 'Footer', + initialValue: 'Пояснение под группой, как в iOS Settings.', + ), + children: const [ + AppListTile(title: 'Строка', value: 'Значение'), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/feedback/progress_use_cases.dart b/mobile/widgetbook/lib/use_cases/feedback/progress_use_cases.dart new file mode 100644 index 0000000..6128513 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/feedback/progress_use_cases.dart @@ -0,0 +1,48 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent progressComponent() { + return WidgetbookComponent( + name: 'Progress', + useCases: [ + WidgetbookUseCase( + name: 'Activity indicator', + builder: (context) { + return IosPreview.centered( + child: AppSpinner( + radius: knobDouble(context, label: 'Radius', initialValue: 14, min: 6, max: 28), + color: knobTint(context), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Progress bar', + builder: (context) { + return IosPreview( + stretch: true, + padding: const EdgeInsets.all(AppSpacing.s4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AppProgressBar( + value: knobDouble(context, label: 'Value', initialValue: 0.42), + color: knobTint(context), + height: knobDouble(context, label: 'Height', initialValue: 4, min: 2, max: 12), + ), + const SizedBox(height: AppSpacing.s4), + const AppProgressBar(value: 0.15, color: AppColors.systemRed), + const SizedBox(height: AppSpacing.s4), + const AppProgressBar(value: 1, color: AppColors.systemGreen), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/feedback/skeletons_use_cases.dart b/mobile/widgetbook/lib/use_cases/feedback/skeletons_use_cases.dart new file mode 100644 index 0000000..1368492 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/feedback/skeletons_use_cases.dart @@ -0,0 +1,38 @@ +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent skeletonsComponent() { + return WidgetbookComponent( + name: 'AppSkeleton', + useCases: [ + WidgetbookUseCase( + name: 'Block', + builder: (context) { + return IosPreview.centered( + child: AppSkeleton( + width: knobDouble(context, label: 'Width', initialValue: 200, min: 40, max: 320), + height: knobDouble(context, label: 'Height', initialValue: 16, min: 8, max: 80), + radius: knobDouble(context, label: 'Radius', initialValue: 6, min: 0, max: 24), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'List loading', + builder: (context) { + final rows = knobInt(context, label: 'Rows', initialValue: 4, min: 1, max: 8); + return IosPreview( + stretch: true, + child: AppListSection( + header: 'Загрузка', + separatorIndent: 60, + children: List.generate(rows, (_) => const AppSkeletonRow()), + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/feedback/toasts_use_cases.dart b/mobile/widgetbook/lib/use_cases/feedback/toasts_use_cases.dart new file mode 100644 index 0000000..b172668 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/feedback/toasts_use_cases.dart @@ -0,0 +1,46 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent toastsComponent() { + return WidgetbookComponent( + name: 'AppToast', + useCases: [ + WidgetbookUseCase( + name: 'Static', + builder: (context) { + return IosPreview.centered( + child: AppToast( + message: knobText(context, label: 'Message', initialValue: 'Трата сохранена'), + tint: knobTint(context), + icon: knobBool(context, label: 'Icon', initialValue: true) + ? CupertinoIcons.check_mark_circled_solid + : null, + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Trigger', + builder: (context) { + return IosPreview( + stretch: true, + child: AppButton( + label: 'Показать toast', + onPressed: () => showAppToast( + context, + message: knobText( + context, + label: 'Message', + initialValue: 'Трата сохранена', + ), + ), + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/molecules/avatars_use_cases.dart b/mobile/widgetbook/lib/use_cases/molecules/avatars_use_cases.dart new file mode 100644 index 0000000..8a1a39b --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/molecules/avatars_use_cases.dart @@ -0,0 +1,48 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent avatarsComponent() { + return WidgetbookComponent( + name: 'AppAvatar', + useCases: [ + WidgetbookUseCase( + name: 'Playground', + builder: (context) { + final initials = knobText(context, label: 'Initials', initialValue: 'ВА'); + return IosPreview.centered( + child: AppAvatar( + initials: knobBool(context, label: 'Use initials', initialValue: true) + ? initials + : null, + radius: knobDouble(context, label: 'Radius', initialValue: 32, min: 12, max: 56), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Sizes', + builder: (context) { + return const IosPreview.centered( + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + AppAvatar(initials: 'А', radius: 16), + SizedBox(width: AppSpacing.s4), + AppAvatar(initials: 'ВА', radius: 22), + SizedBox(width: AppSpacing.s4), + AppAvatar(radius: 32), + SizedBox(width: AppSpacing.s4), + AppAvatar(initials: 'ППМ', radius: 44), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/molecules/cards_use_cases.dart b/mobile/widgetbook/lib/use_cases/molecules/cards_use_cases.dart new file mode 100644 index 0000000..1a1005d --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/molecules/cards_use_cases.dart @@ -0,0 +1,72 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent cardsComponent() { + return WidgetbookComponent( + name: 'AppCard', + useCases: [ + WidgetbookUseCase( + name: 'Playground', + builder: (context) { + final subtitle = knobText(context, label: 'Subtitle', initialValue: 'До 25 сентября'); + return IosPreview( + stretch: true, + child: AppCard( + title: knobText(context, label: 'Title', initialValue: 'Бюджет'), + subtitle: subtitle.isEmpty ? null : subtitle, + onTap: knobBool(context, label: 'Tappable') ? () {} : null, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppText.largeTitle( + knobText(context, label: 'Amount', initialValue: '18 420 ₽'), + ), + const SizedBox(height: AppSpacing.s4), + AppProgressBar( + value: knobDouble(context, label: 'Progress', initialValue: 0.42), + ), + ], + ), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Metrics row', + builder: (context) { + return const IosPreview( + stretch: true, + child: AppCard( + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppText.footnote('Доход'), + AppText.title('64 000 ₽'), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppText.footnote('Расход'), + AppText.title('45 580 ₽', color: AppColors.systemRed), + ], + ), + ), + ], + ), + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/molecules/chips_use_cases.dart b/mobile/widgetbook/lib/use_cases/molecules/chips_use_cases.dart new file mode 100644 index 0000000..edc1b17 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/molecules/chips_use_cases.dart @@ -0,0 +1,45 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent chipsComponent() { + return WidgetbookComponent( + name: 'AppChip', + useCases: [ + WidgetbookUseCase( + name: 'Playground', + builder: (context) { + return IosPreview.centered( + child: AppChip( + label: knobText(context, label: 'Label', initialValue: 'Еда'), + selected: knobBool(context, label: 'Selected'), + icon: knobBool(context, label: 'Icon') ? CupertinoIcons.tag_fill : null, + onPressed: () {}, + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Filter row', + builder: (context) { + final selected = knobInt(context, label: 'Selected index', min: 0, max: 3); + const labels = ['Все', 'Еда', 'Транспорт', 'Подписки']; + return IosPreview.centered( + child: Wrap( + spacing: AppSpacing.s2, + runSpacing: AppSpacing.s2, + alignment: WrapAlignment.center, + children: [ + for (final (index, label) in labels.indexed) + AppChip(label: label, selected: index == selected, onPressed: () {}), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/molecules/list_tiles_use_cases.dart b/mobile/widgetbook/lib/use_cases/molecules/list_tiles_use_cases.dart new file mode 100644 index 0000000..87e6183 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/molecules/list_tiles_use_cases.dart @@ -0,0 +1,80 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/knobs/state_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent listTilesComponent() { + return WidgetbookComponent( + name: 'AppListTile', + useCases: [ + WidgetbookUseCase( + name: 'Playground', + builder: (context) { + final subtitle = knobText(context, label: 'Subtitle', initialValue: 'Супермаркет'); + final value = knobText(context, label: 'Value', initialValue: '−1 840 ₽'); + return IosPreview( + stretch: true, + child: AppListSection( + separatorIndent: 60, + children: [ + AppListTile( + leading: knobBool(context, label: 'Leading badge', initialValue: true) + ? const AppIconBadge(icon: CupertinoIcons.cart_fill) + : null, + title: knobText(context, label: 'Title', initialValue: 'Продукты'), + subtitle: subtitle.isEmpty ? null : subtitle, + value: value.isEmpty ? null : value, + destructive: knobBool(context, label: 'Destructive'), + showChevron: knobBool(context, label: 'Chevron', initialValue: true), + onTap: knobBool(context, label: 'Tappable', initialValue: true) ? () {} : null, + ), + ], + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Grouped section', + builder: (context) { + final state = knobAsyncState(context); + return IosPreview( + stretch: true, + child: AppListSection( + header: 'Сегодня', + footer: 'Свайп по строке открывает действия.', + separatorIndent: 60, + children: [ + wrapAsyncState( + context: context, + state: state, + builder: () => const Column( + children: [ + AppListTile( + leading: AppIconBadge(icon: CupertinoIcons.cart_fill), + title: 'Продукты', + subtitle: 'Супермаркет', + value: '−1 840 ₽', + ), + AppListTile( + leading: AppIconBadge( + icon: CupertinoIcons.car_fill, + color: AppColors.systemOrange, + ), + title: 'Такси', + subtitle: 'Транспорт', + value: '−420 ₽', + ), + ], + ), + ), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/molecules/switches_use_cases.dart b/mobile/widgetbook/lib/use_cases/molecules/switches_use_cases.dart new file mode 100644 index 0000000..5442f62 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/molecules/switches_use_cases.dart @@ -0,0 +1,75 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent switchesComponent() { + return WidgetbookComponent( + name: 'AppSwitchRow', + useCases: [ + WidgetbookUseCase( + name: 'Playground', + builder: (context) { + final subtitle = knobText(context, label: 'Subtitle', initialValue: ''); + return IosPreview( + stretch: true, + child: AppListSection( + separatorIndent: 60, + children: [ + AppSwitchRow( + leading: knobBool(context, label: 'Leading badge', initialValue: true) + ? const AppIconBadge( + icon: CupertinoIcons.bell_fill, + color: AppColors.systemRed, + ) + : null, + title: knobText(context, label: 'Title', initialValue: 'Уведомления'), + subtitle: subtitle.isEmpty ? null : subtitle, + value: knobBool(context, label: 'Value', initialValue: true), + onChanged: knobBool(context, label: 'Enabled', initialValue: true) + ? (_) {} + : null, + ), + ], + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Settings group', + builder: (context) { + return IosPreview( + stretch: true, + child: AppListSection( + header: 'Настройки', + footer: 'Push приходят за час до дедлайна бюджета.', + separatorIndent: 60, + children: [ + AppSwitchRow( + leading: const AppIconBadge( + icon: CupertinoIcons.bell_fill, + color: AppColors.systemRed, + ), + title: 'Уведомления', + value: true, + onChanged: (_) {}, + ), + AppSwitchRow( + leading: const AppIconBadge( + icon: CupertinoIcons.lock_fill, + color: AppColors.systemGray, + ), + title: 'Face ID', + value: false, + onChanged: (_) {}, + ), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/navigation/dialogs_use_cases.dart b/mobile/widgetbook/lib/use_cases/navigation/dialogs_use_cases.dart new file mode 100644 index 0000000..1e9b6b7 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/navigation/dialogs_use_cases.dart @@ -0,0 +1,73 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent dialogsComponent() { + return WidgetbookComponent( + name: 'AppAlert', + useCases: [ + WidgetbookUseCase( + name: 'Alert', + builder: (context) { + final message = knobText( + context, + label: 'Message', + initialValue: 'Операция будет удалена без возможности восстановления.', + ); + return IosPreview.centered( + child: AppAlert( + title: knobText(context, label: 'Title', initialValue: 'Удалить трату?'), + message: message.isEmpty ? null : message, + confirmLabel: knobText(context, label: 'Confirm', initialValue: 'Удалить'), + cancelLabel: knobBool(context, label: 'Cancel button', initialValue: true) + ? 'Отмена' + : null, + destructive: knobBool(context, label: 'Destructive', initialValue: true), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Triggers', + builder: (context) { + return IosPreview( + stretch: true, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AppButton( + label: 'Показать alert', + onPressed: () => showAppAlert( + context: context, + title: 'Удалить трату?', + message: 'Действие необратимо.', + confirmLabel: 'Удалить', + cancelLabel: 'Отмена', + destructive: true, + ), + ), + const SizedBox(height: AppSpacing.s3), + AppButton( + label: 'Показать action sheet', + style: AppButtonStyle.gray, + onPressed: () => showAppActionSheet( + context: context, + title: 'Операция', + actions: const [ + AppActionSheetAction(label: 'Редактировать', isDefault: true), + AppActionSheetAction(label: 'Дублировать'), + AppActionSheetAction(label: 'Удалить', destructive: true), + ], + ), + ), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/navigation/nav_bar_use_cases.dart b/mobile/widgetbook/lib/use_cases/navigation/nav_bar_use_cases.dart new file mode 100644 index 0000000..9ee2d55 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/navigation/nav_bar_use_cases.dart @@ -0,0 +1,83 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent navBarComponent() { + return WidgetbookComponent( + name: 'AppNavBar', + useCases: [ + WidgetbookUseCase( + name: 'Playground', + builder: (context) { + final subtitle = knobText(context, label: 'Subtitle', initialValue: 'До 25 сентября'); + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + navigationBar: AppNavBar( + title: knobText(context, label: 'Title', initialValue: 'Бюджет'), + subtitle: subtitle.isEmpty ? null : subtitle, + previousPageTitle: + knobBool(context, label: 'Back button', initialValue: true) ? 'Назад' : null, + transparent: knobBool(context, label: 'Transparent'), + trailing: knobBool(context, label: 'Trailing action', initialValue: true) + ? CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () {}, + child: const AppIcon( + CupertinoIcons.ellipsis_circle, + color: AppColors.accent, + ), + ) + : null, + ), + child: const SafeArea( + child: Padding( + padding: EdgeInsets.all(AppSpacing.s4), + child: AppText.subhead('Контент под навигационной панелью'), + ), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Large title', + builder: (context) { + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + child: CustomScrollView( + slivers: [ + AppLargeNavBar( + title: knobText(context, label: 'Title', initialValue: 'Журнал'), + trailing: CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () {}, + child: const AppIcon( + CupertinoIcons.add_circled, + color: AppColors.accent, + ), + ), + ), + SliverToBoxAdapter( + child: AppListSection( + header: 'Сегодня', + separatorIndent: 60, + children: const [ + AppListTile( + leading: AppIconBadge(icon: CupertinoIcons.cart_fill), + title: 'Продукты', + value: '−1 840 ₽', + ), + ], + ), + ), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/navigation/segmented_control_use_cases.dart b/mobile/widgetbook/lib/use_cases/navigation/segmented_control_use_cases.dart new file mode 100644 index 0000000..43e7c68 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/navigation/segmented_control_use_cases.dart @@ -0,0 +1,93 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:please_pay_me_widgetbook/support/preview.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent segmentedControlComponent() { + return WidgetbookComponent( + name: 'AppSegmentedControl', + useCases: [ + WidgetbookUseCase( + name: 'Playground', + builder: (context) { + final labels = knobText( + context, + label: 'Labels (comma separated)', + initialValue: 'Все,Расходы,Доходы', + ).split(',').where((e) => e.trim().isNotEmpty).toList(); + + final safeLabels = labels.isEmpty ? ['Все'] : labels; + + return IosPreview( + stretch: true, + child: _StatefulSegments( + labels: safeLabels, + initialIndex: knobInt( + context, + label: 'Initial index', + max: safeLabels.length - 1, + ), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'In screen header', + builder: (context) { + return CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + navigationBar: const AppNavBar(title: 'Журнал'), + child: SafeArea( + child: Column( + children: [ + const SizedBox(height: AppSpacing.s4), + const _StatefulSegments(labels: ['Неделя', 'Месяц', 'Год']), + const SizedBox(height: AppSpacing.s5), + AppListSection( + children: const [ + AppListTile(title: 'Итого', value: '45 580 ₽'), + ], + ), + ], + ), + ), + ); + }, + ), + ], + ); +} + +class _StatefulSegments extends StatefulWidget { + const _StatefulSegments({required this.labels, this.initialIndex = 0}); + + final List labels; + final int initialIndex; + + @override + State<_StatefulSegments> createState() => _StatefulSegmentsState(); +} + +class _StatefulSegmentsState extends State<_StatefulSegments> { + late int _index = widget.initialIndex; + + @override + void didUpdateWidget(_StatefulSegments oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.initialIndex != widget.initialIndex || + _index >= widget.labels.length) { + _index = widget.initialIndex.clamp(0, widget.labels.length - 1); + } + } + + @override + Widget build(BuildContext context) { + return AppSegmentedControl( + labels: widget.labels, + index: _index, + onChanged: (i) => setState(() => _index = i), + ); + } +} diff --git a/mobile/widgetbook/lib/use_cases/navigation/tab_bar_use_cases.dart b/mobile/widgetbook/lib/use_cases/navigation/tab_bar_use_cases.dart new file mode 100644 index 0000000..041f0fe --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/navigation/tab_bar_use_cases.dart @@ -0,0 +1,53 @@ +import 'package:flutter/cupertino.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me/ui/ui.dart'; +import 'package:please_pay_me_widgetbook/knobs/common_knobs.dart'; +import 'package:widgetbook/widgetbook.dart'; + +const _items = [ + AppTabItem(icon: CupertinoIcons.list_bullet, label: 'Журнал'), + AppTabItem(icon: CupertinoIcons.chart_pie, label: 'Бюджет'), + AppTabItem( + icon: CupertinoIcons.person, + activeIcon: CupertinoIcons.person_fill, + label: 'Профиль', + ), +]; + +WidgetbookComponent tabBarComponent() { + return WidgetbookComponent( + name: 'AppTabBar', + useCases: [ + WidgetbookUseCase( + name: 'Playground', + builder: (context) { + final index = knobInt(context, label: 'Current index', min: 0, max: 2); + return ColoredBox( + color: AppColors.of(context, AppColors.groupedBackground), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + AppTabBar(items: _items, currentIndex: index, onTap: (_) {}), + ], + ), + ); + }, + ), + WidgetbookUseCase( + name: 'In scaffold', + builder: (context) { + return CupertinoTabScaffold( + tabBar: AppTabBar(items: _items, currentIndex: 0, onTap: (_) {}), + tabBuilder: (context, index) => CupertinoTabView( + builder: (context) => CupertinoPageScaffold( + backgroundColor: AppColors.of(context, AppColors.groupedBackground), + navigationBar: AppNavBar(title: _items[index].label), + child: Center(child: AppText.body(_items[index].label)), + ), + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/screens/app_screens_use_cases.dart b/mobile/widgetbook/lib/use_cases/screens/app_screens_use_cases.dart new file mode 100644 index 0000000..d7ed05d --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/screens/app_screens_use_cases.dart @@ -0,0 +1,130 @@ +import 'package:please_pay_me/app/home_tabs.dart'; +import 'package:please_pay_me/features/auth/login_screen.dart'; +import 'package:please_pay_me/features/budgets/budgets_screen.dart'; +import 'package:please_pay_me/features/journal/journal_screen.dart'; +import 'package:please_pay_me/features/overview/overview_screen.dart'; +import 'package:please_pay_me/features/profile/profile_screen.dart'; +import 'package:please_pay_me/features/splash/splash_screen.dart'; +import 'package:please_pay_me/features/work/work_screen.dart'; +import 'package:please_pay_me_widgetbook/support/demo_scope.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent overviewScreenComponent() { + return WidgetbookComponent( + name: 'OverviewScreen', + useCases: [ + WidgetbookUseCase( + name: 'С активным бюджетом', + builder: (context) => const DemoScope(child: OverviewScreen()), + ), + WidgetbookUseCase( + name: 'Без бюджетов', + builder: (context) => const DemoScope(seeded: false, child: OverviewScreen()), + ), + ], + ); +} + +WidgetbookComponent journalScreenComponent() { + return WidgetbookComponent( + name: 'JournalScreen', + useCases: [ + WidgetbookUseCase( + name: 'С операциями', + builder: (context) => const DemoScope(child: JournalScreen()), + ), + WidgetbookUseCase( + name: 'Пустой', + builder: (context) => const DemoScope(seeded: false, child: JournalScreen()), + ), + ], + ); +} + +WidgetbookComponent budgetsScreenComponent() { + return WidgetbookComponent( + name: 'BudgetsScreen', + useCases: [ + WidgetbookUseCase( + name: 'Активные и завершённые', + builder: (context) => const DemoScope(child: BudgetsScreen()), + ), + WidgetbookUseCase( + name: 'Пустой', + builder: (context) => const DemoScope(seeded: false, child: BudgetsScreen()), + ), + ], + ); +} + +WidgetbookComponent workScreenComponent() { + return WidgetbookComponent( + name: 'WorkScreen', + useCases: [ + WidgetbookUseCase( + name: 'С работой', + builder: (context) => const DemoScope(child: WorkScreen()), + ), + WidgetbookUseCase( + name: 'Пустой', + builder: (context) => const DemoScope(seeded: false, child: WorkScreen()), + ), + ], + ); +} + +WidgetbookComponent profileScreenComponent() { + return WidgetbookComponent( + name: 'ProfileScreen', + useCases: [ + WidgetbookUseCase( + name: 'Демо-сессия', + builder: (context) => const DemoScope(child: ProfileScreen()), + ), + ], + ); +} + +WidgetbookComponent splashScreenComponent() { + return WidgetbookComponent( + name: 'SplashScreen', + useCases: [ + WidgetbookUseCase( + name: 'Запуск', + builder: (context) => const SplashScreen(), + ), + ], + ); +} + +WidgetbookComponent loginScreenComponent() { + return WidgetbookComponent( + name: 'LoginScreen', + useCases: [ + WidgetbookUseCase( + name: 'Вход через Telegram', + builder: (context) => DemoScope( + // Desktop has no WebView, so the catalog stubs the launcher to show + // the mobile layout. + child: LoginScreen(launchTelegramLogin: (_, __) async => null), + ), + ), + WidgetbookUseCase( + name: 'Вход по токену', + builder: (context) => const DemoScope(child: LoginScreen()), + ), + ], + ); +} + +WidgetbookComponent homeTabsComponent() { + return WidgetbookComponent( + name: 'HomeTabs', + useCases: [ + WidgetbookUseCase( + name: 'Полное приложение', + builder: (context) => const DemoScope(child: HomeTabs()), + ), + ], + ); +} diff --git a/mobile/widgetbook/lib/use_cases/screens/sheets_use_cases.dart b/mobile/widgetbook/lib/use_cases/screens/sheets_use_cases.dart new file mode 100644 index 0000000..84fa1d0 --- /dev/null +++ b/mobile/widgetbook/lib/use_cases/screens/sheets_use_cases.dart @@ -0,0 +1,64 @@ +import 'package:please_pay_me/data/demo/demo_backend.dart'; +import 'package:please_pay_me/features/budgets/budget_form_sheet.dart'; +import 'package:please_pay_me/features/expenses/expense_form_sheet.dart'; +import 'package:please_pay_me/features/work/job_form_sheet.dart'; +import 'package:please_pay_me_widgetbook/support/demo_scope.dart'; +import 'package:widgetbook/widgetbook.dart'; + +WidgetbookComponent sheetsComponent() { + return WidgetbookComponent( + name: 'Формы', + useCases: [ + WidgetbookUseCase( + name: 'Новая трата', + builder: (context) => DemoScope( + child: ExpenseFormSheet( + budgetName: 'До аванса', + remainingToday: 1420, + onSubmit: ({required amount, note, required spentAt}) async => null, + ), + ), + ), + WidgetbookUseCase( + name: 'Новый бюджет', + builder: (context) => DemoScope( + child: BudgetFormSheet( + onSubmit: ({ + required name, + required totalAmount, + required startDate, + required endDate, + required resetExpenses, + }) async => + null, + ), + ), + ), + WidgetbookUseCase( + name: 'Новая работа', + builder: (context) => DemoScope( + child: JobFormSheet( + onSubmit: ({ + required name, + required salaryAmount, + required payDays, + required firstPayPercent, + required weekendPolicy, + }) async => + null, + ), + ), + ), + WidgetbookUseCase( + name: 'Ошибка сохранения', + builder: (context) => DemoScope( + child: ExpenseFormSheet( + budgetName: DemoBackend.user.displayName, + onSubmit: ({required amount, note, required spentAt}) async => + 'Бюджет уже завершён', + ), + ), + ), + ], + ); +} diff --git a/mobile/widgetbook/pubspec.lock b/mobile/widgetbook/pubspec.lock new file mode 100644 index 0000000..414995a --- /dev/null +++ b/mobile/widgetbook/pubspec.lock @@ -0,0 +1,918 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "1b0e6a07425a3e460666e88bf1c949ccc7bb0116ad562ce94a1eca60fe820725" + url: "https://pub.dev" + source: hosted + version: "103.0.0" + accessibility_tools: + dependency: transitive + description: + name: accessibility_tools + sha256: c29732e423175a51e0a6ace7df1255f5d77812c7c7b7101d8ba0186a43d533aa + url: "https://pub.dev" + source: hosted + version: "2.8.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "61c04d0c1bfed555c681ea079519933f071a5a026578ff73c4ff0df2d3462e5e" + url: "https://pub.dev" + source: hosted + version: "13.3.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "8a5c5761af8e31748bba3c82f68925ace40f3225c3eea25be9beb57eca7cd7a8" + url: "https://pub.dev" + source: hosted + version: "4.0.11" + build_config: + dependency: transitive + description: + name: build_config + sha256: d466ed2dc9c6cd1d169948879b84ee061eb5e22c64a7c6089879c6296d272a8d + url: "https://pub.dev" + source: hosted + version: "1.3.3" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: e1d40ef3f7934986d5da2271b1ba07794921ce263e44d622fb6c406d76589e33 + url: "https://pub.dev" + source: hosted + version: "4.1.6" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "894c243f6bc32015fec466ce30a6925bd537a77a426ee5bf481120477eb3de67" + url: "https://pub.dev" + source: hosted + version: "2.16.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: f87ea98192116f7093cb214551ce1929caae0681fdba282b3d8b4462adee7bb7 + url: "https://pub.dev" + source: hosted + version: "8.13.0" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + clock: + dependency: transitive + description: + name: clock + sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e + url: "https://pub.dev" + source: hosted + version: "1.1.3" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: aa5932e94c6c39c2f9ec4e5e06dfdd11a9430a61f6c41b6ba75b28ce0c481baf + url: "https://pub.dev" + source: hosted + version: "4.12.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "82ade9fc4273f29ed673e33166944465225b4f7fc5d4aaef48605cc751c18fc1" + url: "https://pub.dev" + source: hosted + version: "3.1.13" + device_frame_plus: + dependency: transitive + description: + name: device_frame_plus + sha256: ccc94abccd4d9f0a9f19ef239001b3a59896e678ad42601371d7065889f2bf78 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "218aeb56050c714f62a3182775320dfa04602b55074873e24e31bbd39bda96fb" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + google_fonts: + dependency: "direct dev" + description: + name: google_fonts + sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055 + url: "https://pub.dev" + source: hosted + version: "6.3.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + inspector: + dependency: transitive + description: + name: inspector + sha256: "60782d94c8851b0eee3ffdfea9fd43e7c37f919b54cb587e4de9da0e09d491b5" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" + url: "https://pub.dev" + source: hosted + version: "0.20.3" + io: + dependency: transitive + description: + name: io + sha256: "2635216ca6a737e60de577ffa1a48a0bec76ca8a62917cfc1bb88c14c570646f" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + jni: + dependency: transitive + description: + name: jni + sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: b2310cdd4c18c65c081ab141a41efa94aa26c65431803703ece51996f174f351 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_util: + dependency: transitive + description: + name: jni_util + sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" + url: "https://pub.dev" + source: hosted + version: "0.12.20" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d + url: "https://pub.dev" + source: hosted + version: "1.18.3" + mime: + dependency: transitive + description: + name: mime + sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e + url: "https://pub.dev" + source: hosted + version: "9.5.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d + url: "https://pub.dev" + source: hosted + version: "3.0.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec + url: "https://pub.dev" + source: hosted + version: "3.2.0" + please_pay_me: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.1.0+1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "4177f68c237ea2128d1bee66ac17b2ce05ba3dbaafcbdd54c5d40a39d0b6b11c" + url: "https://pub.dev" + source: hosted + version: "1.5.3" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c38b81cbf34450b67e0265d73433569d12e34782e30ed769c9cc99c9d5f2e796 + url: "https://pub.dev" + source: hosted + version: "1.6.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + resizable_widget: + dependency: transitive + description: + name: resizable_widget + sha256: db2919754b93f386b9b3fb15e9f48f6c9d6d41f00a24397629133c99df86606a + url: "https://pub.dev" + source: hosted + version: "1.0.5" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "1e12aafe408aa50da80edfd679a2a6bf63ba7ab37c7fa98286da459a757b3399" + url: "https://pub.dev" + source: hosted + version: "2.4.28" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea" + url: "https://pub.dev" + source: hosted + version: "2.5.7" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: a603f1fb984a7391ae5978d1b92bfaaa08b350dca5c825256f925818f7943bf5 + url: "https://pub.dev" + source: hosted + version: "4.2.4" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490" + url: "https://pub.dev" + source: hosted + version: "1.12.2" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: a00e5f18bffc764f923e7dec1038527f7fe7a1791361a7117f0358193f13d53a + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" + url: "https://pub.dev" + source: hosted + version: "0.7.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "611e87fb320b70d1dd721dc46af89c98aceccea9b31fde49e084591414e0c610" + url: "https://pub.dev" + source: hosted + version: "6.3.33" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "8faa1aab294f1ab4040b43660c887b0418d5fa4f0cffef76a484e6aa1092eb4a" + url: "https://pub.dev" + source: hosted + version: "6.4.2" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "10f86fef4c2c43563fa6c211ff9cf757adf4d3ab762c56bd430664a947d70cd0" + url: "https://pub.dev" + source: hosted + version: "3.2.3" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "5e835a3b869c2d70325349c81c5a45c28e20791265b67b2669da6b08c5cd5201" + url: "https://pub.dev" + source: hosted + version: "3.2.6" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "6c5ad3f22cd4c38e089b81963b3cd7bb83b111b2df5dce008bb066162f42e429" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "1d774bbdf6b72a0b12122fc1560c9c2d2a67db5a4a4cc2bd8a5c990ab20e3188" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" + url: "https://pub.dev" + source: hosted + version: "15.3.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: d53e1ccf5516f25017e3c9d44c39034db352d20fa34fe200674270242c2c5111 + url: "https://pub.dev" + source: hosted + version: "4.14.1" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: "4de8b3d1ff4ebe1bdb42e68a5e4f809194a3cb0117a8f495f590004f00da3964" + url: "https://pub.dev" + source: hosted + version: "4.14.1" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04" + url: "https://pub.dev" + source: hosted + version: "2.15.1" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: fe359c7fac1002124b5b9e2ba3a41906bbb9b2d029ccb4a0067404d8f3704730 + url: "https://pub.dev" + source: hosted + version: "3.26.1" + widgetbook: + dependency: "direct main" + description: + name: widgetbook + sha256: "88b10102d294d0bec64ca294c81f15b1a620ce66df950067ff8f89274f1c95e8" + url: "https://pub.dev" + source: hosted + version: "3.25.0" + widgetbook_annotation: + dependency: "direct main" + description: + name: widgetbook_annotation + sha256: ec98130f23579f14e304b6b45516612676ae4e0b0e6bb1debe9dba4ea604ab42 + url: "https://pub.dev" + source: hosted + version: "3.11.0" + widgetbook_generator: + dependency: "direct dev" + description: + name: widgetbook_generator + sha256: "8fd3b4208bb74cbe60cd6fc99b443561b65542658b20d6dd700a2496edbd5647" + url: "https://pub.dev" + source: hosted + version: "3.24.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea + url: "https://pub.dev" + source: hosted + version: "3.1.4" +sdks: + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/mobile/widgetbook/pubspec.yaml b/mobile/widgetbook/pubspec.yaml new file mode 100644 index 0000000..0c26c94 --- /dev/null +++ b/mobile/widgetbook/pubspec.yaml @@ -0,0 +1,31 @@ +name: please_pay_me_widgetbook +description: Portable Widgetbook catalog for Please Pay Me UI kit +publish_to: "none" +version: 0.1.0+1 + +environment: + sdk: ">=3.5.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + intl: any + please_pay_me: + path: ../ + provider: ^6.1.2 + widgetbook: ^3.14.0 + widgetbook_annotation: ^3.5.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + build_runner: ^2.4.13 + # Smoke tests disable runtime font fetching. + google_fonts: ^6.2.1 + widgetbook_generator: ^3.13.0 + +flutter: + uses-material-design: true diff --git a/mobile/widgetbook/run.ps1 b/mobile/widgetbook/run.ps1 new file mode 100644 index 0000000..6e1a072 --- /dev/null +++ b/mobile/widgetbook/run.ps1 @@ -0,0 +1,60 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Запуск Widgetbook (каталог UI-компонентов). + +.EXAMPLE + .\run.ps1 + .\run.ps1 -Device windows + .\run.ps1 -Device chrome -SkipPubGet +#> +param( + [ValidateSet("chrome", "windows", "edge")] + [string]$Device = "chrome", + + [switch]$SkipPubGet +) + +$ErrorActionPreference = "Stop" +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $ScriptDir + +function Find-Flutter { + $cmd = Get-Command flutter -ErrorAction SilentlyContinue + if ($cmd) { return $cmd.Source } + + $candidates = @( + "$env:USERPROFILE\flutter\bin\flutter.bat", + "$env:LOCALAPPDATA\flutter\bin\flutter.bat", + "C:\flutter\bin\flutter.bat", + "C:\src\flutter\bin\flutter.bat" + ) + foreach ($path in $candidates) { + if (Test-Path $path) { return $path } + } + return $null +} + +$flutter = Find-Flutter +if (-not $flutter) { + Write-Error @" +Flutter не найден в PATH и в стандартных путях. +Установи SDK или добавь в PATH, например: + `$env:Path = `"$env:USERPROFILE\flutter\bin;`$env:Path`" +"@ +} + +Write-Host "Flutter: $flutter" -ForegroundColor DarkGray +Write-Host "Device: $Device" -ForegroundColor DarkGray +Write-Host "Dir: $ScriptDir" -ForegroundColor DarkGray +Write-Host "" + +if (-not $SkipPubGet) { + Write-Host ">> flutter pub get" -ForegroundColor Cyan + & $flutter pub get + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} + +Write-Host ">> flutter run -d $Device" -ForegroundColor Cyan +& $flutter run -d $Device +exit $LASTEXITCODE diff --git a/mobile/widgetbook/test/catalog_smoke_test.dart b/mobile/widgetbook/test/catalog_smoke_test.dart new file mode 100644 index 0000000..0e181ec --- /dev/null +++ b/mobile/widgetbook/test/catalog_smoke_test.dart @@ -0,0 +1,89 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:please_pay_me/theme/theme.dart'; +import 'package:please_pay_me_widgetbook/catalog.dart'; +import 'package:widgetbook/widgetbook.dart'; + +List _collectUseCases() { + final useCases = []; + + void walk(WidgetbookNode node) { + if (node is WidgetbookUseCase) useCases.add(node); + for (final child in node.children ?? const []) { + walk(child); + } + } + + for (final root in buildCatalogDirectories()) { + walk(root); + } + + return useCases; +} + +/// Knobs read `WidgetbookState` from the context, so use-cases can only be +/// pumped inside a scope. +Widget _host({required CupertinoThemeData theme, required WidgetbookUseCase useCase}) { + return WidgetbookScope( + state: WidgetbookState( + path: useCase.path, + root: WidgetbookRoot(children: buildCatalogDirectories()), + ), + child: CupertinoApp( + theme: theme, + locale: const Locale('ru'), + home: Builder(builder: useCase.builder), + ), + ); +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + // No network access in tests — fall back to the default font instead. + GoogleFonts.config.allowRuntimeFetching = false; + + setUpAll(() => initializeDateFormatting('ru')); + + test('catalog directories are non-empty', () { + expect(buildCatalogDirectories(), isNotEmpty); + }); + + test('every use-case has a name and a builder', () { + final useCases = _collectUseCases(); + + for (final useCase in useCases) { + expect(useCase.name, isNotEmpty); + expect(useCase.builder, isNotNull); + } + + expect(useCases.length, greaterThanOrEqualTo(20)); + }); + + test('expected category folders exist', () { + final names = buildCatalogDirectories().map((n) => n.name).toSet(); + expect( + names, + containsAll({'Atoms', 'Molecules', 'Navigation', 'Feedback', 'Screens'}), + ); + }); + + group('use-cases render', () { + final themes = { + 'Light': buildLightTheme, + 'Dark': buildDarkTheme, + }; + + for (final entry in themes.entries) { + for (final useCase in _collectUseCases()) { + testWidgets('${useCase.path} (${entry.key})', (tester) async { + await tester.pumpWidget(_host(theme: entry.value(), useCase: useCase)); + await tester.pump(const Duration(milliseconds: 300)); + + expect(tester.takeException(), isNull); + }); + } + } + }); +} diff --git a/mobile/widgetbook/test/widget_test.dart b/mobile/widgetbook/test/widget_test.dart new file mode 100644 index 0000000..b2c7438 --- /dev/null +++ b/mobile/widgetbook/test/widget_test.dart @@ -0,0 +1,10 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:please_pay_me_widgetbook/app.dart'; + +void main() { + testWidgets('WidgetbookRoot builds', (WidgetTester tester) async { + await tester.pumpWidget(const WidgetbookRoot()); + await tester.pump(); + expect(find.byType(WidgetbookRoot), findsOneWidget); + }); +} diff --git a/mobile/widgetbook/web/favicon.png b/mobile/widgetbook/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/mobile/widgetbook/web/favicon.png differ diff --git a/mobile/widgetbook/web/icons/Icon-192.png b/mobile/widgetbook/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/mobile/widgetbook/web/icons/Icon-192.png differ diff --git a/mobile/widgetbook/web/icons/Icon-512.png b/mobile/widgetbook/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/mobile/widgetbook/web/icons/Icon-512.png differ diff --git a/mobile/widgetbook/web/icons/Icon-maskable-192.png b/mobile/widgetbook/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/mobile/widgetbook/web/icons/Icon-maskable-192.png differ diff --git a/mobile/widgetbook/web/icons/Icon-maskable-512.png b/mobile/widgetbook/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/mobile/widgetbook/web/icons/Icon-maskable-512.png differ diff --git a/mobile/widgetbook/web/index.html b/mobile/widgetbook/web/index.html new file mode 100644 index 0000000..e5ac571 --- /dev/null +++ b/mobile/widgetbook/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + please_pay_me_widgetbook + + + + + + + diff --git a/mobile/widgetbook/web/manifest.json b/mobile/widgetbook/web/manifest.json new file mode 100644 index 0000000..5e91df3 --- /dev/null +++ b/mobile/widgetbook/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "please_pay_me_widgetbook", + "short_name": "please_pay_me_widgetbook", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/mobile/widgetbook/windows/.gitignore b/mobile/widgetbook/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/mobile/widgetbook/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/mobile/widgetbook/windows/CMakeLists.txt b/mobile/widgetbook/windows/CMakeLists.txt new file mode 100644 index 0000000..d6714b6 --- /dev/null +++ b/mobile/widgetbook/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(please_pay_me_widgetbook LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "please_pay_me_widgetbook") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/mobile/widgetbook/windows/flutter/CMakeLists.txt b/mobile/widgetbook/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/mobile/widgetbook/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/mobile/widgetbook/windows/flutter/generated_plugin_registrant.cc b/mobile/widgetbook/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..4f78848 --- /dev/null +++ b/mobile/widgetbook/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/mobile/widgetbook/windows/flutter/generated_plugin_registrant.h b/mobile/widgetbook/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/mobile/widgetbook/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/mobile/widgetbook/windows/flutter/generated_plugins.cmake b/mobile/widgetbook/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..a962892 --- /dev/null +++ b/mobile/widgetbook/windows/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/mobile/widgetbook/windows/runner/CMakeLists.txt b/mobile/widgetbook/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/mobile/widgetbook/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/mobile/widgetbook/windows/runner/Runner.rc b/mobile/widgetbook/windows/runner/Runner.rc new file mode 100644 index 0000000..5fdf920 --- /dev/null +++ b/mobile/widgetbook/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "please_pay_me_widgetbook" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "please_pay_me_widgetbook" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "please_pay_me_widgetbook.exe" "\0" + VALUE "ProductName", "please_pay_me_widgetbook" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/mobile/widgetbook/windows/runner/flutter_window.cpp b/mobile/widgetbook/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/mobile/widgetbook/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/mobile/widgetbook/windows/runner/flutter_window.h b/mobile/widgetbook/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/mobile/widgetbook/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/mobile/widgetbook/windows/runner/main.cpp b/mobile/widgetbook/windows/runner/main.cpp new file mode 100644 index 0000000..7f54b9b --- /dev/null +++ b/mobile/widgetbook/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"please_pay_me_widgetbook", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/mobile/widgetbook/windows/runner/resource.h b/mobile/widgetbook/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/mobile/widgetbook/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/mobile/widgetbook/windows/runner/resources/app_icon.ico b/mobile/widgetbook/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/mobile/widgetbook/windows/runner/resources/app_icon.ico differ diff --git a/mobile/widgetbook/windows/runner/runner.exe.manifest b/mobile/widgetbook/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/mobile/widgetbook/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/mobile/widgetbook/windows/runner/utils.cpp b/mobile/widgetbook/windows/runner/utils.cpp new file mode 100644 index 0000000..3cb7146 --- /dev/null +++ b/mobile/widgetbook/windows/runner/utils.cpp @@ -0,0 +1,69 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + // First, find the length of the string with a safe upper bound (CWE-126). + // UNICODE_STRING_MAX_CHARS (32767) is the maximum length of a UNICODE_STRING. + int input_length = static_cast(wcsnlen(utf16_string, UNICODE_STRING_MAX_CHARS)); + // Now use that bounded length to determine the required buffer size. + // When an explicit length is passed, WideCharToMultiByte does not include + // the null terminator in its returned size. + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || static_cast(target_length) > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/mobile/widgetbook/windows/runner/utils.h b/mobile/widgetbook/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/mobile/widgetbook/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/mobile/widgetbook/windows/runner/win32_window.cpp b/mobile/widgetbook/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/mobile/widgetbook/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/mobile/widgetbook/windows/runner/win32_window.h b/mobile/widgetbook/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/mobile/widgetbook/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/mobile/windows/.gitignore b/mobile/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/mobile/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/mobile/windows/CMakeLists.txt b/mobile/windows/CMakeLists.txt new file mode 100644 index 0000000..1612f79 --- /dev/null +++ b/mobile/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(please_pay_me LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "please_pay_me") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/mobile/windows/flutter/CMakeLists.txt b/mobile/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/mobile/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/mobile/windows/flutter/generated_plugin_registrant.cc b/mobile/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..4f78848 --- /dev/null +++ b/mobile/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/mobile/windows/flutter/generated_plugin_registrant.h b/mobile/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/mobile/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/mobile/windows/flutter/generated_plugins.cmake b/mobile/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..a962892 --- /dev/null +++ b/mobile/windows/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/mobile/windows/runner/CMakeLists.txt b/mobile/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/mobile/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/mobile/windows/runner/Runner.rc b/mobile/windows/runner/Runner.rc new file mode 100644 index 0000000..e6d0312 --- /dev/null +++ b/mobile/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.pleasepayme" "\0" + VALUE "FileDescription", "Dozhit do ZP" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "please_pay_me" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.pleasepayme. All rights reserved." "\0" + VALUE "OriginalFilename", "please_pay_me.exe" "\0" + VALUE "ProductName", "Dozhit do ZP" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/mobile/windows/runner/flutter_window.cpp b/mobile/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/mobile/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/mobile/windows/runner/flutter_window.h b/mobile/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/mobile/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/mobile/windows/runner/main.cpp b/mobile/windows/runner/main.cpp new file mode 100644 index 0000000..188cf0c --- /dev/null +++ b/mobile/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"\x0414\x043e\x0436\x0438\x0442\x044c \x0434\x043e \x0417\x041f", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/mobile/windows/runner/resource.h b/mobile/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/mobile/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/mobile/windows/runner/resources/app_icon.ico b/mobile/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..212e33b Binary files /dev/null and b/mobile/windows/runner/resources/app_icon.ico differ diff --git a/mobile/windows/runner/runner.exe.manifest b/mobile/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/mobile/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/mobile/windows/runner/utils.cpp b/mobile/windows/runner/utils.cpp new file mode 100644 index 0000000..3cb7146 --- /dev/null +++ b/mobile/windows/runner/utils.cpp @@ -0,0 +1,69 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + // First, find the length of the string with a safe upper bound (CWE-126). + // UNICODE_STRING_MAX_CHARS (32767) is the maximum length of a UNICODE_STRING. + int input_length = static_cast(wcsnlen(utf16_string, UNICODE_STRING_MAX_CHARS)); + // Now use that bounded length to determine the required buffer size. + // When an explicit length is passed, WideCharToMultiByte does not include + // the null terminator in its returned size. + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || static_cast(target_length) > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/mobile/windows/runner/utils.h b/mobile/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/mobile/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/mobile/windows/runner/win32_window.cpp b/mobile/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/mobile/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/mobile/windows/runner/win32_window.h b/mobile/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/mobile/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/requirements-api.txt b/requirements-api.txt new file mode 100644 index 0000000..babedd0 --- /dev/null +++ b/requirements-api.txt @@ -0,0 +1,2 @@ +fastapi==0.116.1 +uvicorn[standard]==0.35.0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b2bc7f3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +aiogram==3.22.0 +aiosqlite==0.21.0 +python-dotenv==1.1.1 +pydantic-settings==2.10.1 +aiohttp-socks==0.10.1 +fastapi==0.116.1 +uvicorn[standard]==0.35.0 +PyJWT==2.10.1 diff --git a/scripts/open_xray_listen.sh b/scripts/open_xray_listen.sh new file mode 100644 index 0000000..72ecdd4 --- /dev/null +++ b/scripts/open_xray_listen.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Run on the Ubuntu host (not inside Docker). +# Makes Xray SOCKS/HTTP inbounds listen on 0.0.0.0 so containers can reach them. +set -euo pipefail + +CONFIG="${XRAY_CONFIG:-/usr/local/etc/xray/config.json}" + +if [[ ! -f "$CONFIG" ]]; then + echo "Xray config not found: $CONFIG" >&2 + exit 1 +fi + +if ! command -v python3 >/dev/null; then + echo "python3 is required" >&2 + exit 1 +fi + +sudo python3 - "$CONFIG" <<'PY' +import json +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +data = json.loads(path.read_text(encoding="utf-8")) +changed = False + +for inbound in data.get("inbounds", []): + port = inbound.get("port") + tag = inbound.get("tag", "") + if port in (10808, 10809) or tag in ("socks-in", "http-in"): + if inbound.get("listen") != "0.0.0.0": + inbound["listen"] = "0.0.0.0" + changed = True + print(f"set listen=0.0.0.0 for inbound port={port} tag={tag!r}") + +if not changed: + print("no changes needed (already 0.0.0.0 or ports not found)") +else: + path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(f"updated {path}") +PY + +sudo systemctl restart xray +sudo systemctl --no-pager --full status xray | head -n 20 + +echo +echo "Security: 0.0.0.0 exposes the proxy on all interfaces." +echo "Restrict with firewall, e.g. allow only Docker bridge:" +echo " sudo ufw allow from 172.17.0.0/16 to any port 10808 proto tcp" +echo " sudo ufw allow from 172.17.0.0/16 to any port 10809 proto tcp" +echo " sudo ufw deny 10808/tcp" +echo " sudo ufw deny 10809/tcp" diff --git a/src/PleasePayMe.Api/Auth/JwtTokenService.cs b/src/PleasePayMe.Api/Auth/JwtTokenService.cs new file mode 100644 index 0000000..9660561 --- /dev/null +++ b/src/PleasePayMe.Api/Auth/JwtTokenService.cs @@ -0,0 +1,46 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using PleasePayMe.Api.Options; + +namespace PleasePayMe.Api.Auth; + +public sealed class JwtTokenService(IOptions options) +{ + private readonly AppOptions _options = options.Value; + + public string CreateAccessToken(long userId, string? firstName, string? lastName, string? username) + { + var now = DateTime.UtcNow; + var claims = new List + { + new(JwtRegisteredClaimNames.Sub, userId.ToString()), + new("uid", userId.ToString()), + }; + if (!string.IsNullOrWhiteSpace(firstName)) + { + claims.Add(new Claim("fn", firstName)); + } + + if (!string.IsNullOrWhiteSpace(lastName)) + { + claims.Add(new Claim("ln", lastName)); + } + + if (!string.IsNullOrWhiteSpace(username)) + { + claims.Add(new Claim("un", username)); + } + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.SessionSecret)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + var token = new JwtSecurityToken( + claims: claims, + notBefore: now, + expires: now.AddSeconds(_options.JwtTtlSeconds), + signingCredentials: creds); + return new JwtSecurityTokenHandler().WriteToken(token); + } +} diff --git a/src/PleasePayMe.Api/Auth/RequireApiTokenAttribute.cs b/src/PleasePayMe.Api/Auth/RequireApiTokenAttribute.cs new file mode 100644 index 0000000..9c3f39b --- /dev/null +++ b/src/PleasePayMe.Api/Auth/RequireApiTokenAttribute.cs @@ -0,0 +1,59 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using PleasePayMe.Api.Options; + +namespace PleasePayMe.Api.Auth; + +public sealed class RequireApiTokenAttribute : Attribute, IAsyncActionFilter +{ + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var options = context.HttpContext.RequestServices.GetRequiredService>().Value; + if (string.IsNullOrWhiteSpace(options.ApiToken)) + { + context.Result = new ObjectResult(new { detail = "Admin API_TOKEN is not configured" }) + { + StatusCode = StatusCodes.Status501NotImplemented, + }; + return; + } + + var header = context.HttpContext.Request.Headers["X-API-Token"].FirstOrDefault() + ?? ExtractBearer(context.HttpContext.Request.Headers.Authorization); + if (!string.Equals(header, options.ApiToken, StringComparison.Ordinal)) + { + context.Result = new ObjectResult(new { detail = "Invalid or missing API token" }) + { + StatusCode = StatusCodes.Status401Unauthorized, + }; + return; + } + + await next(); + } + + private static string? ExtractBearer(string? authorization) + { + if (string.IsNullOrWhiteSpace(authorization)) + { + return null; + } + + const string prefix = "Bearer "; + return authorization.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) + ? authorization[prefix.Length..].Trim() + : null; + } +} + +public static class AuthUserExtensions +{ + public static long GetUserId(this ClaimsPrincipal user) + { + var raw = user.FindFirstValue("uid") ?? user.FindFirstValue(ClaimTypes.NameIdentifier) + ?? user.FindFirstValue("sub"); + return long.Parse(raw!); + } +} diff --git a/src/PleasePayMe.Api/Auth/TelegramLoginVerifier.cs b/src/PleasePayMe.Api/Auth/TelegramLoginVerifier.cs new file mode 100644 index 0000000..85f53b2 --- /dev/null +++ b/src/PleasePayMe.Api/Auth/TelegramLoginVerifier.cs @@ -0,0 +1,77 @@ +using System.Security.Cryptography; +using System.Text; +using PleasePayMe.Domain; + +namespace PleasePayMe.Api.Auth; + +public static class TelegramLoginVerifier +{ + public static IReadOnlyDictionary Verify( + IReadOnlyDictionary payload, + string botToken, + int maxAgeSeconds) + { + if (!payload.TryGetValue("hash", out var hashObj) || hashObj is not string receivedHash + || string.IsNullOrWhiteSpace(receivedHash)) + { + throw new DomainException("Missing hash"); + } + + var pairs = payload + .Where(kv => kv.Key != "hash" && kv.Value is not null) + .OrderBy(kv => kv.Key, StringComparer.Ordinal) + .Select(kv => $"{kv.Key}={kv.Value}") + .ToArray(); + var dataCheckString = string.Join("\n", pairs); + + var secretKey = SHA256.HashData(Encoding.UTF8.GetBytes(botToken)); + var calculated = HMACSHA256.HashData(secretKey, Encoding.UTF8.GetBytes(dataCheckString)); + + byte[] received; + try + { + received = Convert.FromHexString(receivedHash); + } + catch (FormatException) + { + throw new DomainException("Invalid Telegram login signature"); + } + + if (received.Length != calculated.Length + || !CryptographicOperations.FixedTimeEquals(calculated, received)) + { + throw new DomainException("Invalid Telegram login signature"); + } + + if (!payload.TryGetValue("auth_date", out var authRaw) + || !long.TryParse(Convert.ToString(authRaw), out var authDate)) + { + throw new DomainException("Invalid auth_date"); + } + + var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + if (maxAgeSeconds > 0 && now - authDate > maxAgeSeconds) + { + throw new DomainException("Telegram login data expired"); + } + + if (!payload.TryGetValue("id", out var idRaw) + || !long.TryParse(Convert.ToString(idRaw), out var userId)) + { + throw new DomainException("Missing Telegram user id"); + } + + return new Dictionary + { + ["id"] = userId, + ["first_name"] = Convert.ToString(payload.GetValueOrDefault("first_name")) ?? "", + ["last_name"] = NullIfEmpty(Convert.ToString(payload.GetValueOrDefault("last_name"))), + ["username"] = NullIfEmpty(Convert.ToString(payload.GetValueOrDefault("username"))), + ["photo_url"] = NullIfEmpty(Convert.ToString(payload.GetValueOrDefault("photo_url"))), + ["auth_date"] = authDate, + }; + } + + private static string? NullIfEmpty(string? value) + => string.IsNullOrWhiteSpace(value) ? null : value; +} diff --git a/src/PleasePayMe.Api/Auth/YandexOAuthClient.cs b/src/PleasePayMe.Api/Auth/YandexOAuthClient.cs new file mode 100644 index 0000000..024ec5e --- /dev/null +++ b/src/PleasePayMe.Api/Auth/YandexOAuthClient.cs @@ -0,0 +1,168 @@ +using System.Net.Http.Headers; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Options; +using PleasePayMe.Api.Options; +using PleasePayMe.Domain; + +namespace PleasePayMe.Api.Auth; + +public sealed record YandexProfile( + long YandexId, + string? Login, + string? FirstName, + string? LastName, + string? PhotoUrl); + +public interface IYandexOAuthClient +{ + Task ExchangeCodeAsync(string code, string redirectUri, CancellationToken cancellationToken); + Task GetProfileAsync(string accessToken, CancellationToken cancellationToken); +} + +public sealed class YandexOAuthClient(HttpClient http, IOptions options) : IYandexOAuthClient +{ + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNameCaseInsensitive = true, + }; + + private readonly AppOptions _options = options.Value; + + public async Task ExchangeCodeAsync(string code, string redirectUri, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(code)) + { + throw new DomainException("Yandex authorization code is missing"); + } + + using var content = new FormUrlEncodedContent(new Dictionary + { + ["grant_type"] = "authorization_code", + ["code"] = code.Trim(), + ["client_id"] = _options.YandexClientId, + ["client_secret"] = _options.YandexClientSecret, + ["redirect_uri"] = redirectUri, + }); + + HttpResponseMessage response; + try + { + response = await http.PostAsync("https://oauth.yandex.ru/token", content, cancellationToken); + } + catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException) + { + throw new DomainException("Не удалось связаться с Яндекс OAuth"); + } + + var raw = await response.Content.ReadAsStringAsync(cancellationToken); + var parsed = Deserialize(raw); + + if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(parsed?.AccessToken)) + { + var detail = FirstNonEmpty(parsed?.ErrorDescription, parsed?.Error, "Yandex rejected the authorization code"); + throw new DomainException(detail); + } + + return parsed.AccessToken; + } + + public async Task GetProfileAsync(string accessToken, CancellationToken cancellationToken) + { + using var request = new HttpRequestMessage(HttpMethod.Get, "https://login.yandex.ru/info?format=json"); + request.Headers.Authorization = new AuthenticationHeaderValue("OAuth", accessToken); + + HttpResponseMessage response; + try + { + response = await http.SendAsync(request, cancellationToken); + } + catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException) + { + throw new DomainException("Не удалось получить профиль Яндекс ID"); + } + + var raw = await response.Content.ReadAsStringAsync(cancellationToken); + if (!response.IsSuccessStatusCode) + { + throw new DomainException("Yandex profile request failed"); + } + + var parsed = Deserialize(raw); + if (parsed is null || !long.TryParse(parsed.Id, out var yandexId) || yandexId <= 0) + { + throw new DomainException("Yandex profile is missing an id"); + } + + var firstName = NullIfEmpty(parsed.FirstName) ?? NullIfEmpty(parsed.DisplayName); + var photoUrl = parsed.IsAvatarEmpty || string.IsNullOrWhiteSpace(parsed.DefaultAvatarId) + ? null + : $"https://avatars.yandex.net/get-yapic/{parsed.DefaultAvatarId}/islands-200"; + + return new YandexProfile( + yandexId, + NullIfEmpty(parsed.Login), + firstName, + NullIfEmpty(parsed.LastName), + photoUrl); + } + + private static T? Deserialize(string raw) + { + if (string.IsNullOrWhiteSpace(raw)) + { + return default; + } + + try + { + return JsonSerializer.Deserialize(raw, JsonOptions); + } + catch (JsonException) + { + return default; + } + } + + private static string? NullIfEmpty(string? value) + => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); + + private static string FirstNonEmpty(params string?[] values) + => values.FirstOrDefault(v => !string.IsNullOrWhiteSpace(v)) ?? "Yandex OAuth failed"; + + private sealed class YandexTokenResponse + { + [JsonPropertyName("access_token")] + public string? AccessToken { get; set; } + + [JsonPropertyName("error")] + public string? Error { get; set; } + + [JsonPropertyName("error_description")] + public string? ErrorDescription { get; set; } + } + + private sealed class YandexProfileResponse + { + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("login")] + public string? Login { get; set; } + + [JsonPropertyName("first_name")] + public string? FirstName { get; set; } + + [JsonPropertyName("last_name")] + public string? LastName { get; set; } + + [JsonPropertyName("display_name")] + public string? DisplayName { get; set; } + + [JsonPropertyName("default_avatar_id")] + public string? DefaultAvatarId { get; set; } + + [JsonPropertyName("is_avatar_empty")] + public bool IsAvatarEmpty { get; set; } + } +} diff --git a/src/PleasePayMe.Api/Contracts/ApiContracts.cs b/src/PleasePayMe.Api/Contracts/ApiContracts.cs new file mode 100644 index 0000000..8b31e63 --- /dev/null +++ b/src/PleasePayMe.Api/Contracts/ApiContracts.cs @@ -0,0 +1,117 @@ +namespace PleasePayMe.Api.Contracts; + +public sealed record BudgetOut( + long Id, + long UserId, + string Name, + decimal TotalAmount, + DateOnly StartDate, + DateOnly EndDate, + string Currency, + bool IsActive); + +public sealed record BudgetStatusOut( + BudgetOut Budget, + DateOnly Today, + int DaysLeft, + decimal TotalSpent, + decimal Remaining, + decimal DailyLimit, + decimal SpentToday, + decimal RemainingToday, + bool IsOverDaily, + bool IsOverBudget, + bool IsExpired, + bool Selected); + +public sealed record ExpenseOut( + long Id, + long BudgetId, + decimal Amount, + string? Note, + DateOnly SpentAt); + +public sealed record ExpensesPageOut( + int Page, + int TotalPages, + int TotalCount, + decimal TotalSum, + int PageSize, + long? BudgetId, + IReadOnlyList Items); + +public sealed record ExpensesRangeOut(IReadOnlyList Items); + +public sealed record BudgetsListOut(IReadOnlyList Items); + +public sealed record AuthUserOut( + long UserId, + string? FirstName, + string? LastName, + string? Username, + string? PhotoUrl); + +public sealed record AuthSessionOut( + string AccessToken, + AuthUserOut User, + string TokenType = "bearer"); + +public sealed record TelegramLoginIn( + long Id, + string FirstName, + string? LastName, + string? Username, + string? PhotoUrl, + long AuthDate, + string Hash); + +public sealed record InternalAuthIn( + long UserId, + string? FirstName, + string? LastName, + string? Username); + +public sealed record YandexLoginIn(string Code, string RedirectUri); + +public sealed record TelegramLinkCompleteIn(string Token); + +public sealed record TelegramLinkCompleteOut(bool Linked); + +public sealed record TelegramLinkRequiredOut(string Detail, string Code, string LoginUrl); + +public sealed record YandexProviderOut(bool Enabled, string? ClientId, string? RedirectUri); + +public sealed record AuthProvidersOut(YandexProviderOut Yandex); + +public sealed record ExpenseCreateIn( + decimal Amount, + string? Note, + DateOnly? SpentAt, + long? BudgetId); + +public sealed record BudgetCreateIn( + decimal TotalAmount, + DateOnly EndDate, + string Name = "Бюджет", + DateOnly? StartDate = null, + bool IsActive = true, + bool Select = true); + +public sealed record BudgetUpdateIn( + decimal? TotalAmount, + DateOnly? EndDate, + DateOnly? StartDate = null, + string? Name = null, + bool ResetExpenses = false); + +public sealed record BudgetActiveIn(bool IsActive); + +public sealed record BudgetUpsertIn( + decimal TotalAmount, + DateOnly EndDate, + bool ResetExpenses = true, + string? Name = null, + long? BudgetId = null, + DateOnly? StartDate = null); + +public sealed record UndoExpenseOut(decimal DeletedAmount, BudgetStatusOut Status); diff --git a/src/PleasePayMe.Api/Contracts/JobContracts.cs b/src/PleasePayMe.Api/Contracts/JobContracts.cs new file mode 100644 index 0000000..efbf234 --- /dev/null +++ b/src/PleasePayMe.Api/Contracts/JobContracts.cs @@ -0,0 +1,44 @@ +using PleasePayMe.Domain; + +namespace PleasePayMe.Api.Contracts; + +public sealed record UpcomingPayOut( + DateOnly Date, + int ScheduledDay, + decimal Percent, + decimal Amount); + +public sealed record JobOut( + long Id, + long UserId, + string Name, + decimal SalaryAmount, + string Currency, + IReadOnlyList PayDays, + decimal FirstPayPercent, + string WeekendPolicy, + bool IsActive, + IReadOnlyList NextPays); + +public sealed record JobsListOut(IReadOnlyList Items); + +public sealed record JobUpsertIn( + string Name, + decimal SalaryAmount, + IReadOnlyList PayDays, + decimal FirstPayPercent = 50, + string WeekendPolicy = "before_weekend", + bool IsActive = true); + +public static class WeekendPolicyCodec +{ + public static WeekendPayPolicy Parse(string? raw) => + (raw ?? "").Trim().ToLowerInvariant() switch + { + "after" or "after_weekend" => WeekendPayPolicy.AfterWeekend, + _ => WeekendPayPolicy.BeforeWeekend, + }; + + public static string Format(WeekendPayPolicy policy) => + policy == WeekendPayPolicy.AfterWeekend ? "after_weekend" : "before_weekend"; +} diff --git a/src/PleasePayMe.Api/Controllers/AdminBudgetsController.cs b/src/PleasePayMe.Api/Controllers/AdminBudgetsController.cs new file mode 100644 index 0000000..c440ff6 --- /dev/null +++ b/src/PleasePayMe.Api/Controllers/AdminBudgetsController.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using PleasePayMe.Api.Auth; +using PleasePayMe.Api.Contracts; +using PleasePayMe.Api.Mapping; +using PleasePayMe.Application.Abstractions; + +namespace PleasePayMe.Api.Controllers; + +[ApiController] +[AllowAnonymous] +[RequireApiToken] +[Route("api/budgets")] +public sealed class AdminBudgetsController(IBudgetService budgets) : ControllerBase +{ + [HttpGet] + public async Task> List(CancellationToken ct) + { + var items = await budgets.ListAllStatusesAsync(ct); + return Ok(new BudgetsListOut(items.Select(ApiMapper.ToOut).ToList())); + } + + [HttpGet("{userId:long}")] + public async Task> Get( + long userId, + [FromQuery(Name = "budget_id")] long? budgetId, + CancellationToken ct) + { + var status = await budgets.GetStatusAsync(userId, budgetId, ct); + return Ok(ApiMapper.ToOut(status)); + } + + [HttpGet("{userId:long}/expenses")] + public async Task> Expenses( + long userId, + [FromQuery] int page = 0, + [FromQuery(Name = "page_size")] int pageSize = 20, + [FromQuery(Name = "spent_at")] DateOnly? spentAt = null, + [FromQuery(Name = "budget_id")] long? budgetId = null, + CancellationToken ct = default) + { + var result = spentAt is null + ? await budgets.GetPeriodExpensesPageAsync(userId, page, pageSize, budgetId, ct) + : await budgets.GetExpensesOnDatePageAsync(userId, spentAt.Value, page, pageSize, budgetId, ct); + return Ok(ApiMapper.ToOut(result)); + } +} diff --git a/src/PleasePayMe.Api/Controllers/AuthController.cs b/src/PleasePayMe.Api/Controllers/AuthController.cs new file mode 100644 index 0000000..dcaee0d --- /dev/null +++ b/src/PleasePayMe.Api/Controllers/AuthController.cs @@ -0,0 +1,110 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using PleasePayMe.Api.Auth; +using PleasePayMe.Api.Contracts; +using PleasePayMe.Api.Options; +using PleasePayMe.Application.Abstractions; +using PleasePayMe.Domain; + +namespace PleasePayMe.Api.Controllers; + +[ApiController] +[Route("api/auth")] +public sealed class AuthController( + JwtTokenService tokens, + IOptions options, + ITelegramLinkService telegramLinks) : ControllerBase +{ + [HttpPost("telegram")] + [AllowAnonymous] + public IActionResult Telegram() + { + return StatusCode( + StatusCodes.Status403Forbidden, + new { detail = "Вход через Telegram отключён. Войдите через Яндекс." }); + } + + [HttpGet("providers")] + [AllowAnonymous] + public ActionResult Providers() + { + var configured = options.Value.IsYandexConfigured; + return Ok(new AuthProvidersOut( + new YandexProviderOut( + configured, + configured ? options.Value.YandexClientId : null, + configured ? options.Value.YandexRedirectUri : null))); + } + + [HttpPost("yandex")] + [AllowAnonymous] + public async Task> Yandex( + [FromBody] YandexLoginIn body, + [FromServices] IYandexOAuthClient yandex, + CancellationToken cancellationToken) + { + if (!options.Value.IsYandexConfigured) + { + throw new DomainException("Yandex login is not configured"); + } + + var redirectUri = (body.RedirectUri ?? "").Trim(); + if (!OAuthRedirectAllowlist.Contains(options.Value.ResolvedYandexRedirectUris, redirectUri)) + { + throw new DomainException("Invalid Yandex redirect_uri"); + } + + var access = await yandex.ExchangeCodeAsync(body.Code, redirectUri, cancellationToken); + var profile = await yandex.GetProfileAsync(access, cancellationToken); + var userId = YandexIdentity.ToInternalUserId(profile.YandexId); + var jwt = tokens.CreateAccessToken(userId, profile.FirstName, profile.LastName, profile.Login); + return Ok(new AuthSessionOut( + jwt, + new AuthUserOut(userId, profile.FirstName, profile.LastName, profile.Login, profile.PhotoUrl))); + } + + [HttpPost("internal")] + [AllowAnonymous] + [RequireApiToken] + public async Task> Internal( + [FromBody] InternalAuthIn body, + CancellationToken cancellationToken) + { + if (body.UserId <= 0 || YandexIdentity.IsYandexUserId(body.UserId)) + { + throw new DomainException("user_id must be a Telegram id"); + } + + var yandexUserId = await telegramLinks.FindYandexUserIdAsync(body.UserId, cancellationToken); + if (yandexUserId is null) + { + var token = await telegramLinks.CreateOrReuseChallengeTokenAsync(body.UserId, cancellationToken); + return StatusCode( + StatusCodes.Status403Forbidden, + new TelegramLinkRequiredOut( + "Чтобы пользоваться ботом, войдите через Яндекс.", + "yandex_required", + options.Value.TelegramYandexLoginUrl(token))); + } + + var accessToken = tokens.CreateAccessToken( + yandexUserId.Value, + body.FirstName, + body.LastName, + body.Username); + return Ok(new AuthSessionOut( + accessToken, + new AuthUserOut(yandexUserId.Value, body.FirstName, body.LastName, body.Username, null))); + } + + [HttpPost("telegram-link/complete")] + [Authorize] + public async Task> CompleteTelegramLink( + [FromBody] TelegramLinkCompleteIn body, + CancellationToken cancellationToken) + { + await telegramLinks.CompleteAsync(body.Token, User.GetUserId(), cancellationToken); + return Ok(new TelegramLinkCompleteOut(true)); + } +} diff --git a/src/PleasePayMe.Api/Controllers/JobsController.cs b/src/PleasePayMe.Api/Controllers/JobsController.cs new file mode 100644 index 0000000..079b056 --- /dev/null +++ b/src/PleasePayMe.Api/Controllers/JobsController.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using PleasePayMe.Api.Auth; +using PleasePayMe.Api.Contracts; +using PleasePayMe.Application.Abstractions; +using PleasePayMe.Application.Contracts; + +namespace PleasePayMe.Api.Controllers; + +[ApiController] +[Authorize] +[Route("api/me/jobs")] +public sealed class JobsController(IJobService jobs) : ControllerBase +{ + [HttpGet] + public async Task> List(CancellationToken ct) + { + var items = await jobs.ListAsync(User.GetUserId(), ct); + return Ok(new JobsListOut(items.Select(ToOut).ToList())); + } + + [HttpGet("{jobId:long}")] + public async Task> Get(long jobId, CancellationToken ct) + { + var job = await jobs.GetAsync(User.GetUserId(), jobId, ct); + return Ok(ToOut(job)); + } + + [HttpPost] + public async Task> Create([FromBody] JobUpsertIn body, CancellationToken ct) + { + var job = await jobs.CreateAsync( + User.GetUserId(), + body.Name, + body.SalaryAmount, + body.PayDays, + body.FirstPayPercent, + WeekendPolicyCodec.Parse(body.WeekendPolicy), + body.IsActive, + ct); + return Ok(ToOut(job)); + } + + [HttpPut("{jobId:long}")] + public async Task> Update( + long jobId, + [FromBody] JobUpsertIn body, + CancellationToken ct) + { + var job = await jobs.UpdateAsync( + User.GetUserId(), + jobId, + body.Name, + body.SalaryAmount, + body.PayDays, + body.FirstPayPercent, + WeekendPolicyCodec.Parse(body.WeekendPolicy), + body.IsActive, + ct); + return Ok(ToOut(job)); + } + + [HttpDelete("{jobId:long}")] + public async Task Delete(long jobId, CancellationToken ct) + { + await jobs.DeleteAsync(User.GetUserId(), jobId, ct); + return NoContent(); + } + + private static JobOut ToOut(JobDto job) => new( + job.Id, + job.UserId, + job.Name, + job.SalaryAmount, + job.Currency, + job.PayDays, + job.FirstPayPercent, + WeekendPolicyCodec.Format(job.WeekendPolicy), + job.IsActive, + job.NextPays.Select(p => new UpcomingPayOut(p.Date, p.ScheduledDay, p.Percent, p.Amount)).ToList()); +} diff --git a/src/PleasePayMe.Api/Controllers/MeController.cs b/src/PleasePayMe.Api/Controllers/MeController.cs new file mode 100644 index 0000000..750fb01 --- /dev/null +++ b/src/PleasePayMe.Api/Controllers/MeController.cs @@ -0,0 +1,187 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using PleasePayMe.Api.Auth; +using PleasePayMe.Api.Contracts; +using PleasePayMe.Api.Mapping; +using PleasePayMe.Application.Abstractions; +using PleasePayMe.Application.Contracts; +using PleasePayMe.Domain; + +namespace PleasePayMe.Api.Controllers; + +[ApiController] +[Authorize] +[Route("api/me")] +public sealed class MeController(IBudgetService budgets) : ControllerBase +{ + [HttpGet] + public ActionResult Me() + { + var user = User; + return Ok(new AuthUserOut( + user.GetUserId(), + user.FindFirst("fn")?.Value, + user.FindFirst("ln")?.Value, + user.FindFirst("un")?.Value, + null)); + } + + [HttpGet("budgets")] + public async Task> ListBudgets(CancellationToken ct) + { + var items = await budgets.ListUserStatusesAsync(User.GetUserId(), ct); + return Ok(new BudgetsListOut(items.Select(ApiMapper.ToOut).ToList())); + } + + [HttpPost("budgets")] + public async Task> CreateBudget( + [FromBody] BudgetCreateIn body, + CancellationToken ct) + { + var status = await budgets.CreateBudgetAsync( + User.GetUserId(), + body.TotalAmount, + body.EndDate, + body.Name, + body.IsActive, + body.Select, + body.StartDate, + ct); + return Ok(ApiMapper.ToOut(status)); + } + + [HttpPut("budgets/{budgetId:long}")] + public async Task> UpdateBudget( + long budgetId, + [FromBody] BudgetUpdateIn body, + CancellationToken ct) + { + var status = await budgets.UpdateBudgetAsync( + User.GetUserId(), + budgetId, + body.Name, + body.TotalAmount, + body.EndDate, + body.StartDate, + body.ResetExpenses, + ct); + return Ok(ApiMapper.ToOut(status)); + } + + [HttpPatch("budgets/{budgetId:long}/active")] + public async Task> SetActive( + long budgetId, + [FromBody] BudgetActiveIn body, + CancellationToken ct) + { + var status = await budgets.SetBudgetActiveAsync(User.GetUserId(), budgetId, body.IsActive, ct); + return Ok(ApiMapper.ToOut(status)); + } + + [HttpDelete("budgets/{budgetId:long}")] + public async Task DeleteBudget(long budgetId, CancellationToken ct) + { + await budgets.DeleteBudgetAsync(User.GetUserId(), budgetId, ct); + return NoContent(); + } + + [HttpPost("budgets/{budgetId:long}/select")] + public async Task> Select(long budgetId, CancellationToken ct) + { + var status = await budgets.SelectBudgetAsync(User.GetUserId(), budgetId, ct); + return Ok(ApiMapper.ToOut(status)); + } + + [HttpGet("budget")] + public async Task> GetBudget( + [FromQuery(Name = "budget_id")] long? budgetId, + CancellationToken ct) + { + var status = await budgets.GetStatusAsync(User.GetUserId(), budgetId, ct); + return Ok(ApiMapper.ToOut(status)); + } + + [HttpPut("budget")] + public async Task> UpsertBudget( + [FromBody] BudgetUpsertIn body, + CancellationToken ct) + { + var status = await budgets.UpsertBudgetAsync( + User.GetUserId(), + body.TotalAmount, + body.EndDate, + body.ResetExpenses, + body.Name, + body.BudgetId, + body.StartDate, + ct); + return Ok(ApiMapper.ToOut(status)); + } + + [HttpGet("expenses")] + public async Task> Expenses( + [FromQuery] int page = 0, + [FromQuery(Name = "page_size")] int pageSize = 20, + [FromQuery(Name = "spent_at")] DateOnly? spentAt = null, + [FromQuery(Name = "budget_id")] long? budgetId = null, + [FromQuery] bool all = false, + CancellationToken ct = default) + { + ExpensesPageDto result; + if (all) + { + result = await budgets.GetAllExpensesPageAsync(User.GetUserId(), page, pageSize, spentAt, ct); + } + else if (spentAt is null) + { + result = await budgets.GetPeriodExpensesPageAsync(User.GetUserId(), page, pageSize, budgetId, ct); + } + else + { + result = await budgets.GetExpensesOnDatePageAsync( + User.GetUserId(), spentAt.Value, page, pageSize, budgetId, ct); + } + + return Ok(ApiMapper.ToOut(result)); + } + + [HttpGet("expenses/range")] + public async Task> ExpensesRange( + [FromQuery(Name = "from")] DateOnly from, + [FromQuery(Name = "to")] DateOnly to, + [FromQuery(Name = "budget_id")] long? budgetId = null, + CancellationToken ct = default) + { + var result = await budgets.GetExpensesInRangeAsync(User.GetUserId(), from, to, budgetId, ct); + return Ok(ApiMapper.ToOut(result)); + } + + [HttpPost("expenses")] + public async Task> CreateExpense( + [FromBody] ExpenseCreateIn body, + CancellationToken ct) + { + var status = await budgets.AddExpenseAsync( + User.GetUserId(), + body.Amount, + body.Note, + body.SpentAt, + body.BudgetId, + ct); + return Ok(ApiMapper.ToOut(status)); + } + + [HttpDelete("expenses/last")] + public async Task> UndoLast( + [FromQuery(Name = "budget_id")] long? budgetId, + CancellationToken ct) + { + var result = await budgets.UndoLastExpenseAsync(User.GetUserId(), budgetId, ct); + if (result is null) + { + throw new DomainException("Нечего отменять"); + } + + return Ok(new UndoExpenseOut(result.Value.DeletedAmount, ApiMapper.ToOut(result.Value.Status))); + } +} diff --git a/src/PleasePayMe.Api/Mapping/ApiMapper.cs b/src/PleasePayMe.Api/Mapping/ApiMapper.cs new file mode 100644 index 0000000..f297d5c --- /dev/null +++ b/src/PleasePayMe.Api/Mapping/ApiMapper.cs @@ -0,0 +1,41 @@ +using PleasePayMe.Api.Contracts; +using PleasePayMe.Application.Contracts; + +namespace PleasePayMe.Api.Mapping; + +public static class ApiMapper +{ + public static BudgetStatusOut ToOut(BudgetStatusDto status) => new( + new BudgetOut( + status.Budget.Id, + status.Budget.UserId, + status.Budget.Name, + status.Budget.TotalAmount, + status.Budget.StartDate, + status.Budget.EndDate, + status.Budget.Currency, + status.Budget.IsActive), + status.Today, + status.DaysLeft, + status.TotalSpent, + status.Remaining, + status.DailyLimit, + status.SpentToday, + status.RemainingToday, + status.IsOverDaily, + status.IsOverBudget, + status.IsExpired, + status.Selected); + + public static ExpensesPageOut ToOut(ExpensesPageDto page) => new( + page.Page, + page.TotalPages, + page.TotalCount, + page.TotalSum, + page.PageSize, + page.Budget?.Id, + page.Items.Select(i => new ExpenseOut(i.Id, i.BudgetId, i.Amount, i.Note, i.SpentAt)).ToList()); + + public static ExpensesRangeOut ToOut(ExpensesRangeDto range) => new( + range.Items.Select(i => new ExpenseOut(i.Id, i.BudgetId, i.Amount, i.Note, i.SpentAt)).ToList()); +} diff --git a/src/PleasePayMe.Api/Middleware/DomainExceptionMiddleware.cs b/src/PleasePayMe.Api/Middleware/DomainExceptionMiddleware.cs new file mode 100644 index 0000000..92a0ec7 --- /dev/null +++ b/src/PleasePayMe.Api/Middleware/DomainExceptionMiddleware.cs @@ -0,0 +1,46 @@ +using System.Net; +using System.Text.Json; +using PleasePayMe.Domain; + +namespace PleasePayMe.Api.Middleware; + +public sealed class DomainExceptionMiddleware(RequestDelegate next) +{ + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + }; + + public async Task InvokeAsync(HttpContext context) + { + try + { + await next(context); + } + catch (DomainException ex) + { + var status = ex.Message.Contains("не найден", StringComparison.OrdinalIgnoreCase) + || ex.Message.Contains("Сначала задай", StringComparison.OrdinalIgnoreCase) + || ex.Message.Contains("Нечего", StringComparison.OrdinalIgnoreCase) + ? HttpStatusCode.NotFound + : HttpStatusCode.BadRequest; + + // Auth-ish messages from telegram verifier + if (ex.Message.Contains("hash", StringComparison.OrdinalIgnoreCase) + || ex.Message.Contains("signature", StringComparison.OrdinalIgnoreCase) + || ex.Message.Contains("expired", StringComparison.OrdinalIgnoreCase) + || ex.Message.Contains("auth_date", StringComparison.OrdinalIgnoreCase) + || ex.Message.Contains("Yandex rejected", StringComparison.OrdinalIgnoreCase) + || ex.Message.Contains("Yandex profile", StringComparison.OrdinalIgnoreCase) + || ex.Message.Contains("authorization code", StringComparison.OrdinalIgnoreCase)) + { + status = HttpStatusCode.Unauthorized; + } + + context.Response.ContentType = "application/json"; + context.Response.StatusCode = (int)status; + await context.Response.WriteAsync( + JsonSerializer.Serialize(new { detail = ex.Message }, JsonOptions)); + } + } +} diff --git a/src/PleasePayMe.Api/Options/AppOptions.cs b/src/PleasePayMe.Api/Options/AppOptions.cs new file mode 100644 index 0000000..0c1915b --- /dev/null +++ b/src/PleasePayMe.Api/Options/AppOptions.cs @@ -0,0 +1,51 @@ +using PleasePayMe.Domain; + +namespace PleasePayMe.Api.Options; + +public sealed class AppOptions +{ + public const string SectionName = "App"; + + public string BotToken { get; set; } = ""; + public string? ApiToken { get; set; } + public string? JwtSecret { get; set; } + public int JwtTtlSeconds { get; set; } = 60 * 60 * 24 * 14; + public int TelegramAuthMaxAgeSeconds { get; set; } = 60 * 60 * 24; + public string CorsOrigins { get; set; } = "*"; + public string YandexClientId { get; set; } = ""; + public string YandexClientSecret { get; set; } = ""; + public string YandexRedirectUri { get; set; } = "https://please-pay-me.ru/"; + public string YandexRedirectUris { get; set; } = ""; + public string PublicWebOrigin { get; set; } = "https://please-pay-me.ru"; + + public string SessionSecret => + string.IsNullOrWhiteSpace(JwtSecret) ? $"ppm-jwt::{BotToken}" : JwtSecret; + + public bool IsYandexConfigured => + !string.IsNullOrWhiteSpace(YandexClientId) && !string.IsNullOrWhiteSpace(YandexClientSecret); + + public IReadOnlyList ResolvedYandexRedirectUris => OAuthRedirectAllowlist.Parse( + YandexRedirectUri, + YandexRedirectUris, + "https://please-pay-me.ru/", + "https://please-pay-me.ru/login", + "http://localhost:51290/", + "http://localhost:51290/login", + "http://127.0.0.1:51290/", + "http://127.0.0.1:51290/login", + "http://localhost:5173/", + "http://localhost:5173/login", + "http://127.0.0.1:5173/", + "http://127.0.0.1:5173/login"); + + public string TelegramYandexLoginUrl(string token) + { + var origin = (PublicWebOrigin ?? "").Trim().TrimEnd('/'); + if (string.IsNullOrWhiteSpace(origin)) + { + origin = "https://please-pay-me.ru"; + } + + return $"{origin}/?tg_link={Uri.EscapeDataString(token)}"; + } +} diff --git a/src/PleasePayMe.Api/PleasePayMe.Api.csproj b/src/PleasePayMe.Api/PleasePayMe.Api.csproj new file mode 100644 index 0000000..cdcd62e --- /dev/null +++ b/src/PleasePayMe.Api/PleasePayMe.Api.csproj @@ -0,0 +1,24 @@ + + + + net9.0 + enable + enable + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/src/PleasePayMe.Api/PleasePayMe.Api.http b/src/PleasePayMe.Api/PleasePayMe.Api.http new file mode 100644 index 0000000..6c325ba --- /dev/null +++ b/src/PleasePayMe.Api/PleasePayMe.Api.http @@ -0,0 +1,6 @@ +@PleasePayMe.Api_HostAddress = http://localhost:5172 + +GET {{PleasePayMe.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/PleasePayMe.Api/Program.cs b/src/PleasePayMe.Api/Program.cs new file mode 100644 index 0000000..8220977 --- /dev/null +++ b/src/PleasePayMe.Api/Program.cs @@ -0,0 +1,116 @@ +using System.Text; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using PleasePayMe.Api.Auth; +using PleasePayMe.Api.Middleware; +using PleasePayMe.Api.Options; +using PleasePayMe.Infrastructure; +using PleasePayMe.Infrastructure.Data; + +var builder = WebApplication.CreateBuilder(args); + +builder.Configuration.AddEnvironmentVariables(); + +builder.Services.Configure(options => +{ + builder.Configuration.GetSection(AppOptions.SectionName).Bind(options); + options.BotToken = builder.Configuration["BOT_TOKEN"] ?? options.BotToken; + options.ApiToken = builder.Configuration["API_TOKEN"] ?? options.ApiToken; + options.JwtSecret = builder.Configuration["JWT_SECRET"] ?? options.JwtSecret; + options.CorsOrigins = builder.Configuration["CORS_ORIGINS"] ?? options.CorsOrigins; + options.YandexClientId = builder.Configuration["YANDEX_CLIENT_ID"] ?? options.YandexClientId; + options.YandexClientSecret = builder.Configuration["YANDEX_CLIENT_SECRET"] ?? options.YandexClientSecret; + options.YandexRedirectUri = builder.Configuration["YANDEX_REDIRECT_URI"] ?? options.YandexRedirectUri; + options.YandexRedirectUris = builder.Configuration["YANDEX_REDIRECT_URIS"] ?? options.YandexRedirectUris; + options.PublicWebOrigin = builder.Configuration["PUBLIC_WEB_ORIGIN"] ?? options.PublicWebOrigin; + if (int.TryParse(builder.Configuration["JWT_TTL_SECONDS"], out var ttl)) + { + options.JwtTtlSeconds = ttl; + } +}); + +var appOptions = new AppOptions(); +builder.Configuration.GetSection(AppOptions.SectionName).Bind(appOptions); +appOptions.BotToken = builder.Configuration["BOT_TOKEN"] ?? appOptions.BotToken; +appOptions.ApiToken = builder.Configuration["API_TOKEN"] ?? appOptions.ApiToken; +appOptions.JwtSecret = builder.Configuration["JWT_SECRET"] ?? appOptions.JwtSecret; +appOptions.CorsOrigins = builder.Configuration["CORS_ORIGINS"] ?? appOptions.CorsOrigins; + +if (string.IsNullOrWhiteSpace(appOptions.BotToken)) +{ + // Allow `dotnet ef` design-time host; runtime compose always sets BOT_TOKEN. + appOptions.BotToken = "design-time-placeholder-token"; +} + +builder.Services.AddInfrastructure(builder.Configuration); +builder.Services.AddHttpClient(client => +{ + client.Timeout = TimeSpan.FromSeconds(15); + client.DefaultRequestHeaders.Accept.ParseAdd("application/json"); +}); +builder.Services.AddSingleton(); +builder.Services.AddControllers() + .AddJsonOptions(o => + { + o.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower; + o.JsonSerializerOptions.DictionaryKeyPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower; + o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }); +builder.Services.AddOpenApi(); + +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(o => + { + o.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = false, + ValidateAudience = false, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey( + Encoding.UTF8.GetBytes(appOptions.SessionSecret)), + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(1), + NameClaimType = "uid", + }; + }); +builder.Services.AddAuthorization(); + +var origins = appOptions.CorsOrigins.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); +builder.Services.AddCors(o => +{ + o.AddDefaultPolicy(p => + { + if (origins.Length == 1 && origins[0] == "*") + { + p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); + } + else + { + p.WithOrigins(origins).AllowAnyHeader().AllowAnyMethod().AllowCredentials(); + } + }); +}); + +builder.Services.AddHealthChecks() + .AddDbContextCheck(); + +var app = builder.Build(); + +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + await db.Database.MigrateAsync(); +} + +app.UseMiddleware(); +app.UseCors(); +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapGet("/api/health", () => Results.Ok(new { status = "ok" })); +app.MapHealthChecks("/health"); +app.MapControllers(); + +app.Run(); diff --git a/src/PleasePayMe.Api/Properties/launchSettings.json b/src/PleasePayMe.Api/Properties/launchSettings.json new file mode 100644 index 0000000..eeae669 --- /dev/null +++ b/src/PleasePayMe.Api/Properties/launchSettings.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:51291", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "BOT_TOKEN": "dev-bot-token", + "API_TOKEN": "dev-api-token", + "ConnectionStrings__Default": "Host=localhost;Port=5432;Database=please_pay_me;Username=ppm;Password=ppm" + } + } + } +} diff --git a/src/PleasePayMe.Api/appsettings.Development.json b/src/PleasePayMe.Api/appsettings.Development.json new file mode 100644 index 0000000..34f00ef --- /dev/null +++ b/src/PleasePayMe.Api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Information" + } + } +} diff --git a/src/PleasePayMe.Api/appsettings.json b/src/PleasePayMe.Api/appsettings.json new file mode 100644 index 0000000..28ea80e --- /dev/null +++ b/src/PleasePayMe.Api/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "Default": "Host=localhost;Port=5432;Database=please_pay_me;Username=ppm;Password=ppm" + }, + "App": { + "BotToken": "", + "ApiToken": "", + "JwtSecret": "", + "JwtTtlSeconds": 1209600, + "TelegramAuthMaxAgeSeconds": 86400, + "CorsOrigins": "*" + } +} diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Humanizer.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Humanizer.dll new file mode 100644 index 0000000..c9a7ef8 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Humanizer.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll new file mode 100644 index 0000000..8071f34 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.AspNetCore.OpenApi.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.AspNetCore.OpenApi.dll new file mode 100644 index 0000000..24eee8a Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.AspNetCore.OpenApi.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Bcl.AsyncInterfaces.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Bcl.AsyncInterfaces.dll new file mode 100644 index 0000000..f5f1cee Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Bcl.AsyncInterfaces.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Build.Locator.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Build.Locator.dll new file mode 100644 index 0000000..446d341 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Build.Locator.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll new file mode 100644 index 0000000..2e99f76 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.CSharp.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.CSharp.dll new file mode 100644 index 0000000..8d56de1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.CSharp.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll new file mode 100644 index 0000000..a17c676 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll new file mode 100644 index 0000000..f70a016 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.Workspaces.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.Workspaces.dll new file mode 100644 index 0000000..7253875 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.Workspaces.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.dll new file mode 100644 index 0000000..7d537db Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.CodeAnalysis.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.Abstractions.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.Abstractions.dll new file mode 100644 index 0000000..81dfef0 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.Design.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.Design.dll new file mode 100644 index 0000000..15e8cfc Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.Design.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.Relational.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.Relational.dll new file mode 100644 index 0000000..7478db2 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.Relational.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.dll new file mode 100644 index 0000000..4f5ff77 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.EntityFrameworkCore.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Caching.Abstractions.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Caching.Abstractions.dll new file mode 100644 index 0000000..bcc2d65 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Caching.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Caching.Memory.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Caching.Memory.dll new file mode 100644 index 0000000..5892a26 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Caching.Memory.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll new file mode 100644 index 0000000..fe3fa69 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll new file mode 100644 index 0000000..0dbff05 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.DependencyInjection.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.DependencyInjection.dll new file mode 100644 index 0000000..2d09b8d Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.DependencyInjection.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.DependencyModel.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.DependencyModel.dll new file mode 100644 index 0000000..38c0549 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.DependencyModel.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll new file mode 100644 index 0000000..8c4da06 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll new file mode 100644 index 0000000..29b77b1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll new file mode 100644 index 0000000..01bbe44 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll new file mode 100644 index 0000000..95de0ff Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll new file mode 100644 index 0000000..1f4deb9 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll new file mode 100644 index 0000000..8494462 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Logging.Abstractions.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Logging.Abstractions.dll new file mode 100644 index 0000000..bef7508 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Logging.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Logging.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Logging.dll new file mode 100644 index 0000000..9df8c85 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Logging.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Options.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Options.dll new file mode 100644 index 0000000..30da524 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Options.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Primitives.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Primitives.dll new file mode 100644 index 0000000..ebf6eee Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.Extensions.Primitives.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Abstractions.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Abstractions.dll new file mode 100644 index 0000000..e981f87 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll new file mode 100644 index 0000000..25f2a7e Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Logging.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Logging.dll new file mode 100644 index 0000000..4ffdb25 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Logging.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll new file mode 100644 index 0000000..6c736d2 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Protocols.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Protocols.dll new file mode 100644 index 0000000..9f30508 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Protocols.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Tokens.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Tokens.dll new file mode 100644 index 0000000..83ec83a Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.IdentityModel.Tokens.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.OpenApi.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.OpenApi.dll new file mode 100644 index 0000000..d9f09da Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Microsoft.OpenApi.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Mono.TextTemplating.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Mono.TextTemplating.dll new file mode 100644 index 0000000..4a76511 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Mono.TextTemplating.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll new file mode 100644 index 0000000..fa6e488 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/Npgsql.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/Npgsql.dll new file mode 100644 index 0000000..241198d Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/Npgsql.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.deps.json b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.deps.json new file mode 100644 index 0000000..0b76941 --- /dev/null +++ b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.deps.json @@ -0,0 +1,1241 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v9.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v9.0": { + "PleasePayMe.Api/1.0.0": { + "dependencies": { + "Microsoft.AspNetCore.Authentication.JwtBearer": "9.0.4", + "Microsoft.AspNetCore.OpenApi": "9.0.17", + "Microsoft.EntityFrameworkCore.Design": "9.0.4", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "9.0.4", + "PleasePayMe.Application": "1.0.0", + "PleasePayMe.Infrastructure": "1.0.0" + }, + "runtime": { + "PleasePayMe.Api.dll": {} + } + }, + "Humanizer.Core/2.14.1": { + "runtime": { + "lib/net6.0/Humanizer.dll": { + "assemblyVersion": "2.14.0.0", + "fileVersion": "2.14.1.48190" + } + } + }, + "Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": { + "dependencies": { + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16403" + } + } + }, + "Microsoft.AspNetCore.OpenApi/9.0.17": { + "dependencies": { + "Microsoft.OpenApi": "1.6.17" + }, + "runtime": { + "lib/net9.0/Microsoft.AspNetCore.OpenApi.dll": { + "assemblyVersion": "9.0.17.0", + "fileVersion": "9.0.1726.26907" + } + } + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "runtime": { + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "Microsoft.Build.Framework/17.8.3": {}, + "Microsoft.Build.Locator/1.7.8": { + "runtime": { + "lib/net6.0/Microsoft.Build.Locator.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.7.8.28074" + } + } + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": {}, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.4", + "System.Collections.Immutable": "7.0.0", + "System.Reflection.Metadata": "7.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "dependencies": { + "Microsoft.CodeAnalysis.Common": "4.8.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.Common": "4.8.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Bcl.AsyncInterfaces": "7.0.0", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "System.Composition": "7.0.0", + "System.IO.Pipelines": "7.0.0", + "System.Threading.Channels": "7.0.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "dependencies": { + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.Common": "4.8.0", + "System.Text.Json": "9.0.4" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + }, + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "9.0.4", + "Microsoft.EntityFrameworkCore.Analyzers": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": {}, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.Build.Locator": "1.7.8", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.MSBuild": "4.8.0", + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyModel": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4", + "Mono.TextTemplating": "3.0.0", + "System.Text.Json": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Design.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll": { + "assemblyVersion": "9.0.0.4", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "9.0.4", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16403" + } + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16403" + } + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Microsoft.Extensions.Diagnostics.HealthChecks": "9.0.4", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16403" + } + } + }, + "Microsoft.Extensions.FileProviders.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Hosting.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.4", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Logging/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Options/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Options.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.Primitives.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.IdentityModel.Abstractions/8.0.1": { + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.IdentityModel.JsonWebTokens/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.IdentityModel.Logging/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Logging.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.IdentityModel.Protocols/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Protocols.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.Protocols": "8.0.1", + "System.IdentityModel.Tokens.Jwt": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.IdentityModel.Tokens/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.Logging": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Tokens.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.OpenApi/1.6.17": { + "runtime": { + "lib/netstandard2.0/Microsoft.OpenApi.dll": { + "assemblyVersion": "1.6.17.0", + "fileVersion": "1.6.17.0" + } + } + }, + "Mono.TextTemplating/3.0.0": { + "dependencies": { + "System.CodeDom": "6.0.0" + }, + "runtime": { + "lib/net6.0/Mono.TextTemplating.dll": { + "assemblyVersion": "3.0.0.0", + "fileVersion": "3.0.0.1" + } + } + }, + "Npgsql/9.0.3": { + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net8.0/Npgsql.dll": { + "assemblyVersion": "9.0.3.0", + "fileVersion": "9.0.3.0" + } + } + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.4", + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Npgsql": "9.0.3" + }, + "runtime": { + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.4.0" + } + } + }, + "System.CodeDom/6.0.0": { + "runtime": { + "lib/net6.0/System.CodeDom.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + } + }, + "System.Collections.Immutable/7.0.0": {}, + "System.Composition/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Convention": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0", + "System.Composition.TypedParts": "7.0.0" + } + }, + "System.Composition.AttributedModel/7.0.0": { + "runtime": { + "lib/net7.0/System.Composition.AttributedModel.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Convention/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.Convention.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Hosting/7.0.0": { + "dependencies": { + "System.Composition.Runtime": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.Hosting.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Runtime/7.0.0": { + "runtime": { + "lib/net7.0/System.Composition.Runtime.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.TypedParts/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.TypedParts.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.IdentityModel.Tokens.Jwt/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "8.0.1", + "Microsoft.IdentityModel.Tokens": "8.0.1" + }, + "runtime": { + "lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "System.IO.Pipelines/7.0.0": {}, + "System.Reflection.Metadata/7.0.0": { + "dependencies": { + "System.Collections.Immutable": "7.0.0" + } + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": {}, + "System.Text.Json/9.0.4": {}, + "System.Threading.Channels/7.0.0": {}, + "PleasePayMe.Application/1.0.0": { + "dependencies": { + "PleasePayMe.Domain": "1.0.0" + }, + "runtime": { + "PleasePayMe.Application.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "PleasePayMe.Domain/1.0.0": { + "runtime": { + "PleasePayMe.Domain.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "PleasePayMe.Infrastructure/1.0.0": { + "dependencies": { + "Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.4", + "PleasePayMe.Application": "1.0.0", + "PleasePayMe.Domain": "1.0.0" + }, + "runtime": { + "PleasePayMe.Infrastructure.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "PleasePayMe.Api/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Humanizer.Core/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==", + "path": "humanizer.core/2.14.1", + "hashPath": "humanizer.core.2.14.1.nupkg.sha512" + }, + "Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0HgfWPfnjlzWFbW4pw6FYNuIMV8obVU+MUkiZ33g4UOpvZcmdWzdayfheKPZ5+EUly8SvfgW0dJwwIrW4IVLZQ==", + "path": "microsoft.aspnetcore.authentication.jwtbearer/9.0.4", + "hashPath": "microsoft.aspnetcore.authentication.jwtbearer.9.0.4.nupkg.sha512" + }, + "Microsoft.AspNetCore.OpenApi/9.0.17": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+zcqQ/JecNl4G1hC2mrJ8qDolJv17W3grToEqcGZGqa3cXWaCjA9KTdigU0WVK3LWI0TtOG/Q/joXRdKqFhB9Q==", + "path": "microsoft.aspnetcore.openapi/9.0.17", + "hashPath": "microsoft.aspnetcore.openapi.9.0.17.nupkg.sha512" + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg==", + "path": "microsoft.bcl.asyncinterfaces/7.0.0", + "hashPath": "microsoft.bcl.asyncinterfaces.7.0.0.nupkg.sha512" + }, + "Microsoft.Build.Framework/17.8.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-NrQZJW8TlKVPx72yltGb8SVz3P5mNRk9fNiD/ao8jRSk48WqIIdCn99q4IjlVmPcruuQ+yLdjNQLL8Rb4c916g==", + "path": "microsoft.build.framework/17.8.3", + "hashPath": "microsoft.build.framework.17.8.3.nupkg.sha512" + }, + "Microsoft.Build.Locator/1.7.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-sPy10x527Ph16S2u0yGME4S6ohBKJ69WfjeGG/bvELYeZVmJdKjxgnlL8cJJJLGV/cZIRqSfB12UDB8ICakOog==", + "path": "microsoft.build.locator/1.7.8", + "hashPath": "microsoft.build.locator.1.7.8.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==", + "path": "microsoft.codeanalysis.analyzers/3.3.4", + "hashPath": "microsoft.codeanalysis.analyzers.3.3.4.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==", + "path": "microsoft.codeanalysis.common/4.8.0", + "hashPath": "microsoft.codeanalysis.common.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==", + "path": "microsoft.codeanalysis.csharp/4.8.0", + "hashPath": "microsoft.codeanalysis.csharp.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3amm4tq4Lo8/BGvg9p3BJh3S9nKq2wqCXfS7138i69TUpo/bD+XvD0hNurpEBtcNZhi1FyutiomKJqVF39ugYA==", + "path": "microsoft.codeanalysis.csharp.workspaces/4.8.0", + "hashPath": "microsoft.codeanalysis.csharp.workspaces.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-LXyV+MJKsKRu3FGJA3OmSk40OUIa/dQCFLOnm5X8MNcujx7hzGu8o+zjXlb/cy5xUdZK2UKYb9YaQ2E8m9QehQ==", + "path": "microsoft.codeanalysis.workspaces.common/4.8.0", + "hashPath": "microsoft.codeanalysis.workspaces.common.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IEYreI82QZKklp54yPHxZNG9EKSK6nHEkeuf+0Asie9llgS1gp0V1hw7ODG+QyoB7MuAnNQHmeV1Per/ECpv6A==", + "path": "microsoft.codeanalysis.workspaces.msbuild/4.8.0", + "hashPath": "microsoft.codeanalysis.workspaces.msbuild.4.8.0.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+5IAX0aicQYCRfN4pAjad+JPwdEYoVEM3Z1Cl8/EiEv3FVHQHdd8TJQpQIslQDDQS/UsUMb0MsOXwqOh+TJtRw==", + "path": "microsoft.entityframeworkcore/9.0.4", + "hashPath": "microsoft.entityframeworkcore.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-E0pkWzI0liqu2ogqJ1kohk2eGkYRhf5tI75HGF6IQDARsshY/0w+prGyLvNuUeV7B8I7vYQZ4CzAKYKxw7b9gQ==", + "path": "microsoft.entityframeworkcore.abstractions/9.0.4", + "hashPath": "microsoft.entityframeworkcore.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cMsm1O7g9X5qbB2wjHf3BVVvGwkG+zeXQ+M91I1Bm6RfylFMImqBPzs0+vmuef7fPxr2yOzPhIfJ2wQJfmtaSw==", + "path": "microsoft.entityframeworkcore.analyzers/9.0.4", + "hashPath": "microsoft.entityframeworkcore.analyzers.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0NdtmsbYfMr2HyF+W6L+kPaHJl1nAmFjWj0MfI5G+CFeWZxDwltQxzzwSmZQ4QhS5z8zjczGXwHZ8e3iFaoiXA==", + "path": "microsoft.entityframeworkcore.design/9.0.4", + "hashPath": "microsoft.entityframeworkcore.design.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-OjJ+xh/wQff5b0wiC3SPvoQqTA2boZeJQf+15+3+OJPtjBKzvxuwr25QRIu1p1t+K8ryQ8pzaoZ7eOpXfNzVGA==", + "path": "microsoft.entityframeworkcore.relational/9.0.4", + "hashPath": "microsoft.entityframeworkcore.relational.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-imcZ5BGhBw5mNsWLepBbqqumWaFe0GtvyCvne2/2wsDIBRa2+Lhx4cU/pKt/4BwOizzUEOls2k1eOJQXHGMalg==", + "path": "microsoft.extensions.caching.abstractions/9.0.4", + "hashPath": "microsoft.extensions.caching.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==", + "path": "microsoft.extensions.caching.memory/9.0.4", + "hashPath": "microsoft.extensions.caching.memory.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0LN/DiIKvBrkqp7gkF3qhGIeZk6/B63PthAHjQsxymJfIBcz0kbf4/p/t4lMgggVxZ+flRi5xvTwlpPOoZk8fg==", + "path": "microsoft.extensions.configuration.abstractions/9.0.4", + "hashPath": "microsoft.extensions.configuration.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-f2MTUaS2EQ3lX4325ytPAISZqgBfXmY0WvgD80ji6Z20AoDNiCESxsqo6mFRwHJD/jfVKRw9FsW6+86gNre3ug==", + "path": "microsoft.extensions.dependencyinjection/9.0.4", + "hashPath": "microsoft.extensions.dependencyinjection.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg==", + "path": "microsoft.extensions.dependencyinjection.abstractions/9.0.4", + "hashPath": "microsoft.extensions.dependencyinjection.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ACtnvl3H3M/f8Z42980JxsNu7V9PPbzys4vBs83ZewnsgKd7JeYK18OMPo0g+MxAHrpgMrjmlinXDiaSRPcVnA==", + "path": "microsoft.extensions.dependencymodel/9.0.4", + "hashPath": "microsoft.extensions.dependencymodel.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Diagnostics.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IAucBcHYtiCmMyFag+Vrp5m+cjGRlDttJk9Vx7Dqpq+Ama4BzVUOk0JARQakgFFr7ZTBSgLKlHmtY5MiItB7Cg==", + "path": "microsoft.extensions.diagnostics.abstractions/9.0.4", + "hashPath": "microsoft.extensions.diagnostics.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Diagnostics.HealthChecks/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jW9lhWQzOOL5sBUCNtAiS6B7tGeLlxJVDjwNuQAQl6dDt9PAAxt3+T2F2jtcvi7KoujgzAdkKQKtGoRaAGlD9w==", + "path": "microsoft.extensions.diagnostics.healthchecks/9.0.4", + "hashPath": "microsoft.extensions.diagnostics.healthchecks.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-XM6WwNbDkVuGhDN89eKxA2Og2eMDXB0PVI7PEzl2R0MbFjYUlfTh7D7vBPEWUVCf2zPDAFiwcMlnVzi6Umq5mg==", + "path": "microsoft.extensions.diagnostics.healthchecks.abstractions/9.0.4", + "hashPath": "microsoft.extensions.diagnostics.healthchecks.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-PdIQYXV2lyBzlQ+zj8+jy+7wxr353MOzOKjqBE2lQWZGFuJZxslmmL8I1gU2+FXE+wGmskSFWZ0n7TZxJu3EgQ==", + "path": "microsoft.extensions.diagnostics.healthchecks.entityframeworkcore/9.0.4", + "hashPath": "microsoft.extensions.diagnostics.healthchecks.entityframeworkcore.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.FileProviders.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-gQN2o/KnBfVk6Bd71E2YsvO5lsqrqHmaepDGk+FB/C4aiQY9B0XKKNKfl5/TqcNOs9OEithm4opiMHAErMFyEw==", + "path": "microsoft.extensions.fileproviders.abstractions/9.0.4", + "hashPath": "microsoft.extensions.fileproviders.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Hosting.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-bXkwRPMo4x19YKH6/V9XotU7KYQJlihXhcWO1RDclAY3yfY3XNg4QtSEBvng4kK/DnboE0O/nwSl+6Jiv9P+FA==", + "path": "microsoft.extensions.hosting.abstractions/9.0.4", + "hashPath": "microsoft.extensions.hosting.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Logging/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-xW6QPYsqhbuWBO9/1oA43g/XPKbohJx+7G8FLQgQXIriYvY7s+gxr2wjQJfRoPO900dvvv2vVH7wZovG+M1m6w==", + "path": "microsoft.extensions.logging/9.0.4", + "hashPath": "microsoft.extensions.logging.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0MXlimU4Dud6t+iNi5NEz3dO2w1HXdhoOLaYFuLPCjAsvlPQGwOT6V2KZRMLEhCAm/stSZt1AUv0XmDdkjvtbw==", + "path": "microsoft.extensions.logging.abstractions/9.0.4", + "hashPath": "microsoft.extensions.logging.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Options/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-fiFI2+58kicqVZyt/6obqoFwHiab7LC4FkQ3mmiBJ28Yy4fAvy2+v9MRnSvvlOO8chTOjKsdafFl/K9veCPo5g==", + "path": "microsoft.extensions.options/9.0.4", + "hashPath": "microsoft.extensions.options.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA==", + "path": "microsoft.extensions.primitives/9.0.4", + "hashPath": "microsoft.extensions.primitives.9.0.4.nupkg.sha512" + }, + "Microsoft.IdentityModel.Abstractions/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-OtlIWcyX01olfdevPKZdIPfBEvbcioDyBiE/Z2lHsopsMD7twcKtlN9kMevHmI5IIPhFpfwCIiR6qHQz1WHUIw==", + "path": "microsoft.identitymodel.abstractions/8.0.1", + "hashPath": "microsoft.identitymodel.abstractions.8.0.1.nupkg.sha512" + }, + "Microsoft.IdentityModel.JsonWebTokens/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-s6++gF9x0rQApQzOBbSyp4jUaAlwm+DroKfL8gdOHxs83k8SJfUXhuc46rDB3rNXBQ1MVRxqKUrqFhO/M0E97g==", + "path": "microsoft.identitymodel.jsonwebtokens/8.0.1", + "hashPath": "microsoft.identitymodel.jsonwebtokens.8.0.1.nupkg.sha512" + }, + "Microsoft.IdentityModel.Logging/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UCPF2exZqBXe7v/6sGNiM6zCQOUXXQ9+v5VTb9gPB8ZSUPnX53BxlN78v2jsbIvK9Dq4GovQxo23x8JgWvm/Qg==", + "path": "microsoft.identitymodel.logging/8.0.1", + "hashPath": "microsoft.identitymodel.logging.8.0.1.nupkg.sha512" + }, + "Microsoft.IdentityModel.Protocols/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-uA2vpKqU3I2mBBEaeJAWPTjT9v1TZrGWKdgK6G5qJd03CLx83kdiqO9cmiK8/n1erkHzFBwU/RphP83aAe3i3g==", + "path": "microsoft.identitymodel.protocols/8.0.1", + "hashPath": "microsoft.identitymodel.protocols.8.0.1.nupkg.sha512" + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-AQDbfpL+yzuuGhO/mQhKNsp44pm5Jv8/BI4KiFXR7beVGZoSH35zMV3PrmcfvSTsyI6qrcR898NzUauD6SRigg==", + "path": "microsoft.identitymodel.protocols.openidconnect/8.0.1", + "hashPath": "microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512" + }, + "Microsoft.IdentityModel.Tokens/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-kDimB6Dkd3nkW2oZPDkMkVHfQt3IDqO5gL0oa8WVy3OP4uE8Ij+8TXnqg9TOd9ufjsY3IDiGz7pCUbnfL18tjg==", + "path": "microsoft.identitymodel.tokens/8.0.1", + "hashPath": "microsoft.identitymodel.tokens.8.0.1.nupkg.sha512" + }, + "Microsoft.OpenApi/1.6.17": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Le+kehlmrlQfuDFUt1zZ2dVwrhFQtKREdKBo+rexOwaCoYP0/qpgT9tLxCsZjsgR5Itk1UKPcbgO+FyaNid/bA==", + "path": "microsoft.openapi/1.6.17", + "hashPath": "microsoft.openapi.1.6.17.nupkg.sha512" + }, + "Mono.TextTemplating/3.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-YqueG52R/Xej4VVbKuRIodjiAhV0HR/XVbLbNrJhCZnzjnSjgMJ/dCdV0akQQxavX6hp/LC6rqLGLcXeQYU7XA==", + "path": "mono.texttemplating/3.0.0", + "hashPath": "mono.texttemplating.3.0.0.nupkg.sha512" + }, + "Npgsql/9.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-tPvY61CxOAWxNsKLEBg+oR646X4Bc8UmyQ/tJszL/7mEmIXQnnBhVJZrZEEUv0Bstu0mEsHZD5At3EO8zQRAYw==", + "path": "npgsql/9.0.3", + "hashPath": "npgsql.9.0.3.nupkg.sha512" + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-mw5vcY2IEc7L+IeGrxpp/J5OSnCcjkjAgJYCm/eD52wpZze8zsSifdqV7zXslSMmfJG2iIUGZyo3KuDtEFKwMQ==", + "path": "npgsql.entityframeworkcore.postgresql/9.0.4", + "hashPath": "npgsql.entityframeworkcore.postgresql.9.0.4.nupkg.sha512" + }, + "System.CodeDom/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==", + "path": "system.codedom/6.0.0", + "hashPath": "system.codedom.6.0.0.nupkg.sha512" + }, + "System.Collections.Immutable/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==", + "path": "system.collections.immutable/7.0.0", + "hashPath": "system.collections.immutable.7.0.0.nupkg.sha512" + }, + "System.Composition/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-tRwgcAkDd85O8Aq6zHDANzQaq380cek9lbMg5Qma46u5BZXq/G+XvIYmu+UI+BIIZ9zssXLYrkTykEqxxvhcmg==", + "path": "system.composition/7.0.0", + "hashPath": "system.composition.7.0.0.nupkg.sha512" + }, + "System.Composition.AttributedModel/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2QzClqjElKxgI1jK1Jztnq44/8DmSuTSGGahXqQ4TdEV0h9s2KikQZIgcEqVzR7OuWDFPGLHIprBJGQEPr8fAQ==", + "path": "system.composition.attributedmodel/7.0.0", + "hashPath": "system.composition.attributedmodel.7.0.0.nupkg.sha512" + }, + "System.Composition.Convention/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IMhTlpCs4HmlD8B+J8/kWfwX7vrBBOs6xyjSTzBlYSs7W4OET4tlkR/Sg9NG8jkdJH9Mymq0qGdYS1VPqRTBnQ==", + "path": "system.composition.convention/7.0.0", + "hashPath": "system.composition.convention.7.0.0.nupkg.sha512" + }, + "System.Composition.Hosting/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-eB6gwN9S+54jCTBJ5bpwMOVerKeUfGGTYCzz3QgDr1P55Gg/Wb27ShfPIhLMjmZ3MoAKu8uUSv6fcCdYJTN7Bg==", + "path": "system.composition.hosting/7.0.0", + "hashPath": "system.composition.hosting.7.0.0.nupkg.sha512" + }, + "System.Composition.Runtime/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-aZJ1Zr5Txe925rbo4742XifEyW0MIni1eiUebmcrP3HwLXZ3IbXUj4MFMUH/RmnJOAQiS401leg/2Sz1MkApDw==", + "path": "system.composition.runtime/7.0.0", + "hashPath": "system.composition.runtime.7.0.0.nupkg.sha512" + }, + "System.Composition.TypedParts/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ZK0KNPfbtxVceTwh+oHNGUOYV2WNOHReX2AXipuvkURC7s/jPwoWfsu3SnDBDgofqbiWr96geofdQ2erm/KTHg==", + "path": "system.composition.typedparts/7.0.0", + "hashPath": "system.composition.typedparts.7.0.0.nupkg.sha512" + }, + "System.IdentityModel.Tokens.Jwt/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-GJw3bYkWpOgvN3tJo5X4lYUeIFA2HD293FPUhKmp7qxS+g5ywAb34Dnd3cDAFLkcMohy5XTpoaZ4uAHuw0uSPQ==", + "path": "system.identitymodel.tokens.jwt/8.0.1", + "hashPath": "system.identitymodel.tokens.jwt.8.0.1.nupkg.sha512" + }, + "System.IO.Pipelines/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg==", + "path": "system.io.pipelines/7.0.0", + "hashPath": "system.io.pipelines.7.0.0.nupkg.sha512" + }, + "System.Reflection.Metadata/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==", + "path": "system.reflection.metadata/7.0.0", + "hashPath": "system.reflection.metadata.7.0.0.nupkg.sha512" + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + "path": "system.runtime.compilerservices.unsafe/6.0.0", + "hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512" + }, + "System.Text.Json/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-pYtmpcO6R3Ef1XilZEHgXP2xBPVORbYEzRP7dl0IAAbN8Dm+kfwio8aCKle97rAWXOExr292MuxWYurIuwN62g==", + "path": "system.text.json/9.0.4", + "hashPath": "system.text.json.9.0.4.nupkg.sha512" + }, + "System.Threading.Channels/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA==", + "path": "system.threading.channels/7.0.0", + "hashPath": "system.threading.channels.7.0.0.nupkg.sha512" + }, + "PleasePayMe.Application/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "PleasePayMe.Infrastructure/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.dll new file mode 100644 index 0000000..8aedc80 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.exe b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.exe new file mode 100644 index 0000000..e418bb7 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.exe differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.pdb b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.pdb new file mode 100644 index 0000000..4d14d50 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.pdb differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.runtimeconfig.json b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.runtimeconfig.json new file mode 100644 index 0000000..1f6a32f --- /dev/null +++ b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.runtimeconfig.json @@ -0,0 +1,20 @@ +{ + "runtimeOptions": { + "tfm": "net9.0", + "frameworks": [ + { + "name": "Microsoft.NETCore.App", + "version": "9.0.0" + }, + { + "name": "Microsoft.AspNetCore.App", + "version": "9.0.0" + } + ], + "configProperties": { + "System.GC.Server": true, + "System.Reflection.NullabilityInfoContext.IsSupported": true, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.staticwebassets.endpoints.json b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.staticwebassets.endpoints.json new file mode 100644 index 0000000..5576e88 --- /dev/null +++ b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Api.staticwebassets.endpoints.json @@ -0,0 +1 @@ +{"Version":1,"ManifestType":"Build","Endpoints":[]} \ No newline at end of file diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Application.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Application.dll new file mode 100644 index 0000000..bfec08b Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Application.pdb b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Application.pdb new file mode 100644 index 0000000..c531d97 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Application.pdb differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..e1642b6 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..9c94240 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Infrastructure.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Infrastructure.dll new file mode 100644 index 0000000..746f02f Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Infrastructure.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Infrastructure.pdb b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Infrastructure.pdb new file mode 100644 index 0000000..583ebf2 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/PleasePayMe.Infrastructure.pdb differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/System.CodeDom.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/System.CodeDom.dll new file mode 100644 index 0000000..54c82b6 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/System.CodeDom.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.AttributedModel.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.AttributedModel.dll new file mode 100644 index 0000000..1431751 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.AttributedModel.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.Convention.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.Convention.dll new file mode 100644 index 0000000..e9dacb1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.Convention.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.Hosting.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.Hosting.dll new file mode 100644 index 0000000..8381202 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.Hosting.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.Runtime.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.Runtime.dll new file mode 100644 index 0000000..d583c3a Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.Runtime.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.TypedParts.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.TypedParts.dll new file mode 100644 index 0000000..2b278d7 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/System.Composition.TypedParts.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/System.IdentityModel.Tokens.Jwt.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/System.IdentityModel.Tokens.Jwt.dll new file mode 100644 index 0000000..c42b8d7 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/System.IdentityModel.Tokens.Jwt.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/appsettings.Development.json b/src/PleasePayMe.Api/bin/Debug/net9.0/appsettings.Development.json new file mode 100644 index 0000000..34f00ef --- /dev/null +++ b/src/PleasePayMe.Api/bin/Debug/net9.0/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Information" + } + } +} diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/appsettings.json b/src/PleasePayMe.Api/bin/Debug/net9.0/appsettings.json new file mode 100644 index 0000000..28ea80e --- /dev/null +++ b/src/PleasePayMe.Api/bin/Debug/net9.0/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "Default": "Host=localhost;Port=5432;Database=please_pay_me;Username=ppm;Password=ppm" + }, + "App": { + "BotToken": "", + "ApiToken": "", + "JwtSecret": "", + "JwtTtlSeconds": 1209600, + "TelegramAuthMaxAgeSeconds": 86400, + "CorsOrigins": "*" + } +} diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..4e90e20 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..8dcc1bd Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..8ee4b4d Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..62b0422 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..180a8d9 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/cs/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..4b7bae7 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..05da79f Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..bd0bb72 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..e128407 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..6a98feb Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/de/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..8e8ced1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..970399e Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..9e6afdd Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..6cb47ac Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..76ddceb Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/es/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..c41ed4c Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..5fe6dd8 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..6eb37cb Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..046c953 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..368bb7b Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/fr/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..72bb9d5 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..6051d99 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..ad0d2cd Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..829ed5d Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..9890df1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/it/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..eaded8c Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..47f3fd5 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..28c43a1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..203cc83 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..208b1d9 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ja/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..895ca11 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..c712a37 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..4d5b1a3 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..4790c29 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..05bc700 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ko/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..eb61aff Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..ea192cc Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..08eaeab Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..fce2d36 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..e142029 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/pl/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..7c20209 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..be86033 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..4be51d2 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..768264c Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..0dc6fae Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/pt-BR/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..85dd902 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..dfd0a6b Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..f5e6b57 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..cafdf21 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..ace0504 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/ru/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..9867f6f Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..2a4742e Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..8977db0 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..8012969 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..9a06288 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/tr/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..e4b3c7a Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..b51ee57 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..d160925 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..e27e8be Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..22b6e95 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..57e4d28 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..305dfbf Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..28a5c18 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..cef3ebc Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..dce3bc0 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Debug/net9.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Humanizer.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Humanizer.dll new file mode 100644 index 0000000..c9a7ef8 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Humanizer.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll new file mode 100644 index 0000000..8071f34 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.AspNetCore.OpenApi.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.AspNetCore.OpenApi.dll new file mode 100644 index 0000000..24eee8a Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.AspNetCore.OpenApi.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Bcl.AsyncInterfaces.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Bcl.AsyncInterfaces.dll new file mode 100644 index 0000000..f5f1cee Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Bcl.AsyncInterfaces.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Build.Locator.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Build.Locator.dll new file mode 100644 index 0000000..446d341 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Build.Locator.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll new file mode 100644 index 0000000..2e99f76 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.CSharp.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.CSharp.dll new file mode 100644 index 0000000..8d56de1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.CSharp.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll new file mode 100644 index 0000000..a17c676 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll new file mode 100644 index 0000000..f70a016 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.Workspaces.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.Workspaces.dll new file mode 100644 index 0000000..7253875 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.Workspaces.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.dll new file mode 100644 index 0000000..7d537db Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.CodeAnalysis.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.Abstractions.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.Abstractions.dll new file mode 100644 index 0000000..81dfef0 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.Design.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.Design.dll new file mode 100644 index 0000000..15e8cfc Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.Design.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.Relational.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.Relational.dll new file mode 100644 index 0000000..7478db2 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.Relational.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.dll new file mode 100644 index 0000000..4f5ff77 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.EntityFrameworkCore.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Caching.Abstractions.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Caching.Abstractions.dll new file mode 100644 index 0000000..bcc2d65 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Caching.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Caching.Memory.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Caching.Memory.dll new file mode 100644 index 0000000..5892a26 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Caching.Memory.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll new file mode 100644 index 0000000..fe3fa69 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll new file mode 100644 index 0000000..0dbff05 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.DependencyInjection.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.DependencyInjection.dll new file mode 100644 index 0000000..2d09b8d Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.DependencyInjection.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.DependencyModel.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.DependencyModel.dll new file mode 100644 index 0000000..38c0549 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.DependencyModel.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll new file mode 100644 index 0000000..8c4da06 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll new file mode 100644 index 0000000..29b77b1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll new file mode 100644 index 0000000..01bbe44 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll new file mode 100644 index 0000000..95de0ff Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll new file mode 100644 index 0000000..1f4deb9 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll new file mode 100644 index 0000000..8494462 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Logging.Abstractions.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Logging.Abstractions.dll new file mode 100644 index 0000000..bef7508 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Logging.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Logging.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Logging.dll new file mode 100644 index 0000000..9df8c85 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Logging.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Options.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Options.dll new file mode 100644 index 0000000..30da524 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Options.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Primitives.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Primitives.dll new file mode 100644 index 0000000..ebf6eee Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.Extensions.Primitives.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Abstractions.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Abstractions.dll new file mode 100644 index 0000000..e981f87 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Abstractions.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll new file mode 100644 index 0000000..25f2a7e Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Logging.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Logging.dll new file mode 100644 index 0000000..4ffdb25 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Logging.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll new file mode 100644 index 0000000..6c736d2 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Protocols.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Protocols.dll new file mode 100644 index 0000000..9f30508 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Protocols.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Tokens.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Tokens.dll new file mode 100644 index 0000000..83ec83a Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.IdentityModel.Tokens.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.OpenApi.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.OpenApi.dll new file mode 100644 index 0000000..d9f09da Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Microsoft.OpenApi.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Mono.TextTemplating.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Mono.TextTemplating.dll new file mode 100644 index 0000000..4a76511 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Mono.TextTemplating.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll new file mode 100644 index 0000000..fa6e488 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/Npgsql.dll b/src/PleasePayMe.Api/bin/Release/net9.0/Npgsql.dll new file mode 100644 index 0000000..241198d Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/Npgsql.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.deps.json b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.deps.json new file mode 100644 index 0000000..0b76941 --- /dev/null +++ b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.deps.json @@ -0,0 +1,1241 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v9.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v9.0": { + "PleasePayMe.Api/1.0.0": { + "dependencies": { + "Microsoft.AspNetCore.Authentication.JwtBearer": "9.0.4", + "Microsoft.AspNetCore.OpenApi": "9.0.17", + "Microsoft.EntityFrameworkCore.Design": "9.0.4", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": "9.0.4", + "PleasePayMe.Application": "1.0.0", + "PleasePayMe.Infrastructure": "1.0.0" + }, + "runtime": { + "PleasePayMe.Api.dll": {} + } + }, + "Humanizer.Core/2.14.1": { + "runtime": { + "lib/net6.0/Humanizer.dll": { + "assemblyVersion": "2.14.0.0", + "fileVersion": "2.14.1.48190" + } + } + }, + "Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": { + "dependencies": { + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16403" + } + } + }, + "Microsoft.AspNetCore.OpenApi/9.0.17": { + "dependencies": { + "Microsoft.OpenApi": "1.6.17" + }, + "runtime": { + "lib/net9.0/Microsoft.AspNetCore.OpenApi.dll": { + "assemblyVersion": "9.0.17.0", + "fileVersion": "9.0.1726.26907" + } + } + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "runtime": { + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "Microsoft.Build.Framework/17.8.3": {}, + "Microsoft.Build.Locator/1.7.8": { + "runtime": { + "lib/net6.0/Microsoft.Build.Locator.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.7.8.28074" + } + } + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": {}, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.4", + "System.Collections.Immutable": "7.0.0", + "System.Reflection.Metadata": "7.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "dependencies": { + "Microsoft.CodeAnalysis.Common": "4.8.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.Common": "4.8.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Bcl.AsyncInterfaces": "7.0.0", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "System.Composition": "7.0.0", + "System.IO.Pipelines": "7.0.0", + "System.Threading.Channels": "7.0.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "dependencies": { + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.Common": "4.8.0", + "System.Text.Json": "9.0.4" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + }, + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "9.0.4", + "Microsoft.EntityFrameworkCore.Analyzers": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": {}, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.Build.Locator": "1.7.8", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.MSBuild": "4.8.0", + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyModel": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4", + "Mono.TextTemplating": "3.0.0", + "System.Text.Json": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Design.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll": { + "assemblyVersion": "9.0.0.4", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "9.0.4", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16403" + } + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16403" + } + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Microsoft.Extensions.Diagnostics.HealthChecks": "9.0.4", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16403" + } + } + }, + "Microsoft.Extensions.FileProviders.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Hosting.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.4", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Logging/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Options/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Options.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.Primitives.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.IdentityModel.Abstractions/8.0.1": { + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.IdentityModel.JsonWebTokens/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.IdentityModel.Logging/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Logging.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.IdentityModel.Protocols/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Protocols.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.Protocols": "8.0.1", + "System.IdentityModel.Tokens.Jwt": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.IdentityModel.Tokens/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.Logging": "8.0.1" + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Tokens.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "Microsoft.OpenApi/1.6.17": { + "runtime": { + "lib/netstandard2.0/Microsoft.OpenApi.dll": { + "assemblyVersion": "1.6.17.0", + "fileVersion": "1.6.17.0" + } + } + }, + "Mono.TextTemplating/3.0.0": { + "dependencies": { + "System.CodeDom": "6.0.0" + }, + "runtime": { + "lib/net6.0/Mono.TextTemplating.dll": { + "assemblyVersion": "3.0.0.0", + "fileVersion": "3.0.0.1" + } + } + }, + "Npgsql/9.0.3": { + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net8.0/Npgsql.dll": { + "assemblyVersion": "9.0.3.0", + "fileVersion": "9.0.3.0" + } + } + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.4", + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Npgsql": "9.0.3" + }, + "runtime": { + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.4.0" + } + } + }, + "System.CodeDom/6.0.0": { + "runtime": { + "lib/net6.0/System.CodeDom.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + } + }, + "System.Collections.Immutable/7.0.0": {}, + "System.Composition/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Convention": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0", + "System.Composition.TypedParts": "7.0.0" + } + }, + "System.Composition.AttributedModel/7.0.0": { + "runtime": { + "lib/net7.0/System.Composition.AttributedModel.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Convention/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.Convention.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Hosting/7.0.0": { + "dependencies": { + "System.Composition.Runtime": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.Hosting.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Runtime/7.0.0": { + "runtime": { + "lib/net7.0/System.Composition.Runtime.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.TypedParts/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.TypedParts.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.IdentityModel.Tokens.Jwt/8.0.1": { + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "8.0.1", + "Microsoft.IdentityModel.Tokens": "8.0.1" + }, + "runtime": { + "lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": { + "assemblyVersion": "8.0.1.0", + "fileVersion": "8.0.1.50722" + } + } + }, + "System.IO.Pipelines/7.0.0": {}, + "System.Reflection.Metadata/7.0.0": { + "dependencies": { + "System.Collections.Immutable": "7.0.0" + } + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": {}, + "System.Text.Json/9.0.4": {}, + "System.Threading.Channels/7.0.0": {}, + "PleasePayMe.Application/1.0.0": { + "dependencies": { + "PleasePayMe.Domain": "1.0.0" + }, + "runtime": { + "PleasePayMe.Application.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "PleasePayMe.Domain/1.0.0": { + "runtime": { + "PleasePayMe.Domain.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "PleasePayMe.Infrastructure/1.0.0": { + "dependencies": { + "Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.4", + "PleasePayMe.Application": "1.0.0", + "PleasePayMe.Domain": "1.0.0" + }, + "runtime": { + "PleasePayMe.Infrastructure.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "PleasePayMe.Api/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Humanizer.Core/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==", + "path": "humanizer.core/2.14.1", + "hashPath": "humanizer.core.2.14.1.nupkg.sha512" + }, + "Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0HgfWPfnjlzWFbW4pw6FYNuIMV8obVU+MUkiZ33g4UOpvZcmdWzdayfheKPZ5+EUly8SvfgW0dJwwIrW4IVLZQ==", + "path": "microsoft.aspnetcore.authentication.jwtbearer/9.0.4", + "hashPath": "microsoft.aspnetcore.authentication.jwtbearer.9.0.4.nupkg.sha512" + }, + "Microsoft.AspNetCore.OpenApi/9.0.17": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+zcqQ/JecNl4G1hC2mrJ8qDolJv17W3grToEqcGZGqa3cXWaCjA9KTdigU0WVK3LWI0TtOG/Q/joXRdKqFhB9Q==", + "path": "microsoft.aspnetcore.openapi/9.0.17", + "hashPath": "microsoft.aspnetcore.openapi.9.0.17.nupkg.sha512" + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg==", + "path": "microsoft.bcl.asyncinterfaces/7.0.0", + "hashPath": "microsoft.bcl.asyncinterfaces.7.0.0.nupkg.sha512" + }, + "Microsoft.Build.Framework/17.8.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-NrQZJW8TlKVPx72yltGb8SVz3P5mNRk9fNiD/ao8jRSk48WqIIdCn99q4IjlVmPcruuQ+yLdjNQLL8Rb4c916g==", + "path": "microsoft.build.framework/17.8.3", + "hashPath": "microsoft.build.framework.17.8.3.nupkg.sha512" + }, + "Microsoft.Build.Locator/1.7.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-sPy10x527Ph16S2u0yGME4S6ohBKJ69WfjeGG/bvELYeZVmJdKjxgnlL8cJJJLGV/cZIRqSfB12UDB8ICakOog==", + "path": "microsoft.build.locator/1.7.8", + "hashPath": "microsoft.build.locator.1.7.8.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==", + "path": "microsoft.codeanalysis.analyzers/3.3.4", + "hashPath": "microsoft.codeanalysis.analyzers.3.3.4.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==", + "path": "microsoft.codeanalysis.common/4.8.0", + "hashPath": "microsoft.codeanalysis.common.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==", + "path": "microsoft.codeanalysis.csharp/4.8.0", + "hashPath": "microsoft.codeanalysis.csharp.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3amm4tq4Lo8/BGvg9p3BJh3S9nKq2wqCXfS7138i69TUpo/bD+XvD0hNurpEBtcNZhi1FyutiomKJqVF39ugYA==", + "path": "microsoft.codeanalysis.csharp.workspaces/4.8.0", + "hashPath": "microsoft.codeanalysis.csharp.workspaces.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-LXyV+MJKsKRu3FGJA3OmSk40OUIa/dQCFLOnm5X8MNcujx7hzGu8o+zjXlb/cy5xUdZK2UKYb9YaQ2E8m9QehQ==", + "path": "microsoft.codeanalysis.workspaces.common/4.8.0", + "hashPath": "microsoft.codeanalysis.workspaces.common.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IEYreI82QZKklp54yPHxZNG9EKSK6nHEkeuf+0Asie9llgS1gp0V1hw7ODG+QyoB7MuAnNQHmeV1Per/ECpv6A==", + "path": "microsoft.codeanalysis.workspaces.msbuild/4.8.0", + "hashPath": "microsoft.codeanalysis.workspaces.msbuild.4.8.0.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+5IAX0aicQYCRfN4pAjad+JPwdEYoVEM3Z1Cl8/EiEv3FVHQHdd8TJQpQIslQDDQS/UsUMb0MsOXwqOh+TJtRw==", + "path": "microsoft.entityframeworkcore/9.0.4", + "hashPath": "microsoft.entityframeworkcore.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-E0pkWzI0liqu2ogqJ1kohk2eGkYRhf5tI75HGF6IQDARsshY/0w+prGyLvNuUeV7B8I7vYQZ4CzAKYKxw7b9gQ==", + "path": "microsoft.entityframeworkcore.abstractions/9.0.4", + "hashPath": "microsoft.entityframeworkcore.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cMsm1O7g9X5qbB2wjHf3BVVvGwkG+zeXQ+M91I1Bm6RfylFMImqBPzs0+vmuef7fPxr2yOzPhIfJ2wQJfmtaSw==", + "path": "microsoft.entityframeworkcore.analyzers/9.0.4", + "hashPath": "microsoft.entityframeworkcore.analyzers.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0NdtmsbYfMr2HyF+W6L+kPaHJl1nAmFjWj0MfI5G+CFeWZxDwltQxzzwSmZQ4QhS5z8zjczGXwHZ8e3iFaoiXA==", + "path": "microsoft.entityframeworkcore.design/9.0.4", + "hashPath": "microsoft.entityframeworkcore.design.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-OjJ+xh/wQff5b0wiC3SPvoQqTA2boZeJQf+15+3+OJPtjBKzvxuwr25QRIu1p1t+K8ryQ8pzaoZ7eOpXfNzVGA==", + "path": "microsoft.entityframeworkcore.relational/9.0.4", + "hashPath": "microsoft.entityframeworkcore.relational.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-imcZ5BGhBw5mNsWLepBbqqumWaFe0GtvyCvne2/2wsDIBRa2+Lhx4cU/pKt/4BwOizzUEOls2k1eOJQXHGMalg==", + "path": "microsoft.extensions.caching.abstractions/9.0.4", + "hashPath": "microsoft.extensions.caching.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==", + "path": "microsoft.extensions.caching.memory/9.0.4", + "hashPath": "microsoft.extensions.caching.memory.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0LN/DiIKvBrkqp7gkF3qhGIeZk6/B63PthAHjQsxymJfIBcz0kbf4/p/t4lMgggVxZ+flRi5xvTwlpPOoZk8fg==", + "path": "microsoft.extensions.configuration.abstractions/9.0.4", + "hashPath": "microsoft.extensions.configuration.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-f2MTUaS2EQ3lX4325ytPAISZqgBfXmY0WvgD80ji6Z20AoDNiCESxsqo6mFRwHJD/jfVKRw9FsW6+86gNre3ug==", + "path": "microsoft.extensions.dependencyinjection/9.0.4", + "hashPath": "microsoft.extensions.dependencyinjection.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg==", + "path": "microsoft.extensions.dependencyinjection.abstractions/9.0.4", + "hashPath": "microsoft.extensions.dependencyinjection.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ACtnvl3H3M/f8Z42980JxsNu7V9PPbzys4vBs83ZewnsgKd7JeYK18OMPo0g+MxAHrpgMrjmlinXDiaSRPcVnA==", + "path": "microsoft.extensions.dependencymodel/9.0.4", + "hashPath": "microsoft.extensions.dependencymodel.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Diagnostics.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IAucBcHYtiCmMyFag+Vrp5m+cjGRlDttJk9Vx7Dqpq+Ama4BzVUOk0JARQakgFFr7ZTBSgLKlHmtY5MiItB7Cg==", + "path": "microsoft.extensions.diagnostics.abstractions/9.0.4", + "hashPath": "microsoft.extensions.diagnostics.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Diagnostics.HealthChecks/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jW9lhWQzOOL5sBUCNtAiS6B7tGeLlxJVDjwNuQAQl6dDt9PAAxt3+T2F2jtcvi7KoujgzAdkKQKtGoRaAGlD9w==", + "path": "microsoft.extensions.diagnostics.healthchecks/9.0.4", + "hashPath": "microsoft.extensions.diagnostics.healthchecks.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-XM6WwNbDkVuGhDN89eKxA2Og2eMDXB0PVI7PEzl2R0MbFjYUlfTh7D7vBPEWUVCf2zPDAFiwcMlnVzi6Umq5mg==", + "path": "microsoft.extensions.diagnostics.healthchecks.abstractions/9.0.4", + "hashPath": "microsoft.extensions.diagnostics.healthchecks.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-PdIQYXV2lyBzlQ+zj8+jy+7wxr353MOzOKjqBE2lQWZGFuJZxslmmL8I1gU2+FXE+wGmskSFWZ0n7TZxJu3EgQ==", + "path": "microsoft.extensions.diagnostics.healthchecks.entityframeworkcore/9.0.4", + "hashPath": "microsoft.extensions.diagnostics.healthchecks.entityframeworkcore.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.FileProviders.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-gQN2o/KnBfVk6Bd71E2YsvO5lsqrqHmaepDGk+FB/C4aiQY9B0XKKNKfl5/TqcNOs9OEithm4opiMHAErMFyEw==", + "path": "microsoft.extensions.fileproviders.abstractions/9.0.4", + "hashPath": "microsoft.extensions.fileproviders.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Hosting.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-bXkwRPMo4x19YKH6/V9XotU7KYQJlihXhcWO1RDclAY3yfY3XNg4QtSEBvng4kK/DnboE0O/nwSl+6Jiv9P+FA==", + "path": "microsoft.extensions.hosting.abstractions/9.0.4", + "hashPath": "microsoft.extensions.hosting.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Logging/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-xW6QPYsqhbuWBO9/1oA43g/XPKbohJx+7G8FLQgQXIriYvY7s+gxr2wjQJfRoPO900dvvv2vVH7wZovG+M1m6w==", + "path": "microsoft.extensions.logging/9.0.4", + "hashPath": "microsoft.extensions.logging.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0MXlimU4Dud6t+iNi5NEz3dO2w1HXdhoOLaYFuLPCjAsvlPQGwOT6V2KZRMLEhCAm/stSZt1AUv0XmDdkjvtbw==", + "path": "microsoft.extensions.logging.abstractions/9.0.4", + "hashPath": "microsoft.extensions.logging.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Options/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-fiFI2+58kicqVZyt/6obqoFwHiab7LC4FkQ3mmiBJ28Yy4fAvy2+v9MRnSvvlOO8chTOjKsdafFl/K9veCPo5g==", + "path": "microsoft.extensions.options/9.0.4", + "hashPath": "microsoft.extensions.options.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA==", + "path": "microsoft.extensions.primitives/9.0.4", + "hashPath": "microsoft.extensions.primitives.9.0.4.nupkg.sha512" + }, + "Microsoft.IdentityModel.Abstractions/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-OtlIWcyX01olfdevPKZdIPfBEvbcioDyBiE/Z2lHsopsMD7twcKtlN9kMevHmI5IIPhFpfwCIiR6qHQz1WHUIw==", + "path": "microsoft.identitymodel.abstractions/8.0.1", + "hashPath": "microsoft.identitymodel.abstractions.8.0.1.nupkg.sha512" + }, + "Microsoft.IdentityModel.JsonWebTokens/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-s6++gF9x0rQApQzOBbSyp4jUaAlwm+DroKfL8gdOHxs83k8SJfUXhuc46rDB3rNXBQ1MVRxqKUrqFhO/M0E97g==", + "path": "microsoft.identitymodel.jsonwebtokens/8.0.1", + "hashPath": "microsoft.identitymodel.jsonwebtokens.8.0.1.nupkg.sha512" + }, + "Microsoft.IdentityModel.Logging/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UCPF2exZqBXe7v/6sGNiM6zCQOUXXQ9+v5VTb9gPB8ZSUPnX53BxlN78v2jsbIvK9Dq4GovQxo23x8JgWvm/Qg==", + "path": "microsoft.identitymodel.logging/8.0.1", + "hashPath": "microsoft.identitymodel.logging.8.0.1.nupkg.sha512" + }, + "Microsoft.IdentityModel.Protocols/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-uA2vpKqU3I2mBBEaeJAWPTjT9v1TZrGWKdgK6G5qJd03CLx83kdiqO9cmiK8/n1erkHzFBwU/RphP83aAe3i3g==", + "path": "microsoft.identitymodel.protocols/8.0.1", + "hashPath": "microsoft.identitymodel.protocols.8.0.1.nupkg.sha512" + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-AQDbfpL+yzuuGhO/mQhKNsp44pm5Jv8/BI4KiFXR7beVGZoSH35zMV3PrmcfvSTsyI6qrcR898NzUauD6SRigg==", + "path": "microsoft.identitymodel.protocols.openidconnect/8.0.1", + "hashPath": "microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512" + }, + "Microsoft.IdentityModel.Tokens/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-kDimB6Dkd3nkW2oZPDkMkVHfQt3IDqO5gL0oa8WVy3OP4uE8Ij+8TXnqg9TOd9ufjsY3IDiGz7pCUbnfL18tjg==", + "path": "microsoft.identitymodel.tokens/8.0.1", + "hashPath": "microsoft.identitymodel.tokens.8.0.1.nupkg.sha512" + }, + "Microsoft.OpenApi/1.6.17": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Le+kehlmrlQfuDFUt1zZ2dVwrhFQtKREdKBo+rexOwaCoYP0/qpgT9tLxCsZjsgR5Itk1UKPcbgO+FyaNid/bA==", + "path": "microsoft.openapi/1.6.17", + "hashPath": "microsoft.openapi.1.6.17.nupkg.sha512" + }, + "Mono.TextTemplating/3.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-YqueG52R/Xej4VVbKuRIodjiAhV0HR/XVbLbNrJhCZnzjnSjgMJ/dCdV0akQQxavX6hp/LC6rqLGLcXeQYU7XA==", + "path": "mono.texttemplating/3.0.0", + "hashPath": "mono.texttemplating.3.0.0.nupkg.sha512" + }, + "Npgsql/9.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-tPvY61CxOAWxNsKLEBg+oR646X4Bc8UmyQ/tJszL/7mEmIXQnnBhVJZrZEEUv0Bstu0mEsHZD5At3EO8zQRAYw==", + "path": "npgsql/9.0.3", + "hashPath": "npgsql.9.0.3.nupkg.sha512" + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-mw5vcY2IEc7L+IeGrxpp/J5OSnCcjkjAgJYCm/eD52wpZze8zsSifdqV7zXslSMmfJG2iIUGZyo3KuDtEFKwMQ==", + "path": "npgsql.entityframeworkcore.postgresql/9.0.4", + "hashPath": "npgsql.entityframeworkcore.postgresql.9.0.4.nupkg.sha512" + }, + "System.CodeDom/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==", + "path": "system.codedom/6.0.0", + "hashPath": "system.codedom.6.0.0.nupkg.sha512" + }, + "System.Collections.Immutable/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==", + "path": "system.collections.immutable/7.0.0", + "hashPath": "system.collections.immutable.7.0.0.nupkg.sha512" + }, + "System.Composition/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-tRwgcAkDd85O8Aq6zHDANzQaq380cek9lbMg5Qma46u5BZXq/G+XvIYmu+UI+BIIZ9zssXLYrkTykEqxxvhcmg==", + "path": "system.composition/7.0.0", + "hashPath": "system.composition.7.0.0.nupkg.sha512" + }, + "System.Composition.AttributedModel/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2QzClqjElKxgI1jK1Jztnq44/8DmSuTSGGahXqQ4TdEV0h9s2KikQZIgcEqVzR7OuWDFPGLHIprBJGQEPr8fAQ==", + "path": "system.composition.attributedmodel/7.0.0", + "hashPath": "system.composition.attributedmodel.7.0.0.nupkg.sha512" + }, + "System.Composition.Convention/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IMhTlpCs4HmlD8B+J8/kWfwX7vrBBOs6xyjSTzBlYSs7W4OET4tlkR/Sg9NG8jkdJH9Mymq0qGdYS1VPqRTBnQ==", + "path": "system.composition.convention/7.0.0", + "hashPath": "system.composition.convention.7.0.0.nupkg.sha512" + }, + "System.Composition.Hosting/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-eB6gwN9S+54jCTBJ5bpwMOVerKeUfGGTYCzz3QgDr1P55Gg/Wb27ShfPIhLMjmZ3MoAKu8uUSv6fcCdYJTN7Bg==", + "path": "system.composition.hosting/7.0.0", + "hashPath": "system.composition.hosting.7.0.0.nupkg.sha512" + }, + "System.Composition.Runtime/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-aZJ1Zr5Txe925rbo4742XifEyW0MIni1eiUebmcrP3HwLXZ3IbXUj4MFMUH/RmnJOAQiS401leg/2Sz1MkApDw==", + "path": "system.composition.runtime/7.0.0", + "hashPath": "system.composition.runtime.7.0.0.nupkg.sha512" + }, + "System.Composition.TypedParts/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ZK0KNPfbtxVceTwh+oHNGUOYV2WNOHReX2AXipuvkURC7s/jPwoWfsu3SnDBDgofqbiWr96geofdQ2erm/KTHg==", + "path": "system.composition.typedparts/7.0.0", + "hashPath": "system.composition.typedparts.7.0.0.nupkg.sha512" + }, + "System.IdentityModel.Tokens.Jwt/8.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-GJw3bYkWpOgvN3tJo5X4lYUeIFA2HD293FPUhKmp7qxS+g5ywAb34Dnd3cDAFLkcMohy5XTpoaZ4uAHuw0uSPQ==", + "path": "system.identitymodel.tokens.jwt/8.0.1", + "hashPath": "system.identitymodel.tokens.jwt.8.0.1.nupkg.sha512" + }, + "System.IO.Pipelines/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg==", + "path": "system.io.pipelines/7.0.0", + "hashPath": "system.io.pipelines.7.0.0.nupkg.sha512" + }, + "System.Reflection.Metadata/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==", + "path": "system.reflection.metadata/7.0.0", + "hashPath": "system.reflection.metadata.7.0.0.nupkg.sha512" + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + "path": "system.runtime.compilerservices.unsafe/6.0.0", + "hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512" + }, + "System.Text.Json/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-pYtmpcO6R3Ef1XilZEHgXP2xBPVORbYEzRP7dl0IAAbN8Dm+kfwio8aCKle97rAWXOExr292MuxWYurIuwN62g==", + "path": "system.text.json/9.0.4", + "hashPath": "system.text.json.9.0.4.nupkg.sha512" + }, + "System.Threading.Channels/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA==", + "path": "system.threading.channels/7.0.0", + "hashPath": "system.threading.channels.7.0.0.nupkg.sha512" + }, + "PleasePayMe.Application/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "PleasePayMe.Infrastructure/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.dll b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.dll new file mode 100644 index 0000000..db4b066 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.exe b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.exe new file mode 100644 index 0000000..e418bb7 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.exe differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.pdb b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.pdb new file mode 100644 index 0000000..3ac9cab Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.pdb differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.runtimeconfig.json b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.runtimeconfig.json new file mode 100644 index 0000000..b10d651 --- /dev/null +++ b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.runtimeconfig.json @@ -0,0 +1,21 @@ +{ + "runtimeOptions": { + "tfm": "net9.0", + "frameworks": [ + { + "name": "Microsoft.NETCore.App", + "version": "9.0.0" + }, + { + "name": "Microsoft.AspNetCore.App", + "version": "9.0.0" + } + ], + "configProperties": { + "System.GC.Server": true, + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Reflection.NullabilityInfoContext.IsSupported": true, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.staticwebassets.endpoints.json b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.staticwebassets.endpoints.json new file mode 100644 index 0000000..5576e88 --- /dev/null +++ b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Api.staticwebassets.endpoints.json @@ -0,0 +1 @@ +{"Version":1,"ManifestType":"Build","Endpoints":[]} \ No newline at end of file diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Application.dll b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Application.dll new file mode 100644 index 0000000..8eec7c1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Application.pdb b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Application.pdb new file mode 100644 index 0000000..5bc04bf Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Application.pdb differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..0c885d2 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..db59d49 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Infrastructure.dll b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Infrastructure.dll new file mode 100644 index 0000000..d1ecdaf Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Infrastructure.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Infrastructure.pdb b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Infrastructure.pdb new file mode 100644 index 0000000..d918482 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/PleasePayMe.Infrastructure.pdb differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/System.CodeDom.dll b/src/PleasePayMe.Api/bin/Release/net9.0/System.CodeDom.dll new file mode 100644 index 0000000..54c82b6 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/System.CodeDom.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.AttributedModel.dll b/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.AttributedModel.dll new file mode 100644 index 0000000..1431751 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.AttributedModel.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.Convention.dll b/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.Convention.dll new file mode 100644 index 0000000..e9dacb1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.Convention.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.Hosting.dll b/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.Hosting.dll new file mode 100644 index 0000000..8381202 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.Hosting.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.Runtime.dll b/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.Runtime.dll new file mode 100644 index 0000000..d583c3a Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.Runtime.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.TypedParts.dll b/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.TypedParts.dll new file mode 100644 index 0000000..2b278d7 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/System.Composition.TypedParts.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/System.IdentityModel.Tokens.Jwt.dll b/src/PleasePayMe.Api/bin/Release/net9.0/System.IdentityModel.Tokens.Jwt.dll new file mode 100644 index 0000000..c42b8d7 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/System.IdentityModel.Tokens.Jwt.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/appsettings.Development.json b/src/PleasePayMe.Api/bin/Release/net9.0/appsettings.Development.json new file mode 100644 index 0000000..34f00ef --- /dev/null +++ b/src/PleasePayMe.Api/bin/Release/net9.0/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Information" + } + } +} diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/appsettings.json b/src/PleasePayMe.Api/bin/Release/net9.0/appsettings.json new file mode 100644 index 0000000..28ea80e --- /dev/null +++ b/src/PleasePayMe.Api/bin/Release/net9.0/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "Default": "Host=localhost;Port=5432;Database=please_pay_me;Username=ppm;Password=ppm" + }, + "App": { + "BotToken": "", + "ApiToken": "", + "JwtSecret": "", + "JwtTtlSeconds": 1209600, + "TelegramAuthMaxAgeSeconds": 86400, + "CorsOrigins": "*" + } +} diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..4e90e20 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..8dcc1bd Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..8ee4b4d Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..62b0422 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..180a8d9 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/cs/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..4b7bae7 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..05da79f Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..bd0bb72 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..e128407 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..6a98feb Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/de/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..8e8ced1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..970399e Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..9e6afdd Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..6cb47ac Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..76ddceb Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/es/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..c41ed4c Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..5fe6dd8 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..6eb37cb Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..046c953 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..368bb7b Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/fr/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..72bb9d5 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..6051d99 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..ad0d2cd Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..829ed5d Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..9890df1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/it/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..eaded8c Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..47f3fd5 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..28c43a1 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..203cc83 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..208b1d9 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ja/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..895ca11 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..c712a37 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..4d5b1a3 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..4790c29 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..05bc700 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ko/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..eb61aff Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..ea192cc Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..08eaeab Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..fce2d36 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..e142029 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/pl/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..7c20209 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..be86033 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..4be51d2 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..768264c Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..0dc6fae Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/pt-BR/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..85dd902 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..dfd0a6b Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..f5e6b57 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..cafdf21 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..ace0504 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/ru/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..9867f6f Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..2a4742e Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..8977db0 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..8012969 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..9a06288 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/tr/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..e4b3c7a Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..b51ee57 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..d160925 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..e27e8be Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..22b6e95 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll new file mode 100644 index 0000000..57e4d28 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll new file mode 100644 index 0000000..305dfbf Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll new file mode 100644 index 0000000..28a5c18 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll new file mode 100644 index 0000000..cef3ebc Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll differ diff --git a/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll new file mode 100644 index 0000000..dce3bc0 Binary files /dev/null and b/src/PleasePayMe.Api/bin/Release/net9.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll differ diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/src/PleasePayMe.Api/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePa.A8AA5E0D.Up2Date b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePa.A8AA5E0D.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.AssemblyInfo.cs b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.AssemblyInfo.cs new file mode 100644 index 0000000..d7df836 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("PleasePayMe.Api")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("PleasePayMe.Api")] +[assembly: System.Reflection.AssemblyTitleAttribute("PleasePayMe.Api")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.AssemblyInfoInputs.cache b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.AssemblyInfoInputs.cache new file mode 100644 index 0000000..f4e4aca --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +1e5ba0cf11fdf1b63b1259ff82d62f88e6ac068bf336edd89e4b9347add2ae06 diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.GeneratedMSBuildEditorConfig.editorconfig b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..899d786 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,29 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = true +build_property.UsingMicrosoftNETSdkWeb = true +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = PleasePayMe.Api +build_property.RootNamespace = PleasePayMe.Api +build_property.ProjectDir = c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.RazorLangVersion = 9.0 +build_property.SupportLocalizedComponentNames = +build_property.GenerateRazorMetadataSourceChecksumAttributes = +build_property.MSBuildProjectDirectory = c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api +build_property._RazorSourceGeneratorDebug = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.GlobalUsings.g.cs b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.GlobalUsings.g.cs new file mode 100644 index 0000000..025530a --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.GlobalUsings.g.cs @@ -0,0 +1,17 @@ +// +global using global::Microsoft.AspNetCore.Builder; +global using global::Microsoft.AspNetCore.Hosting; +global using global::Microsoft.AspNetCore.Http; +global using global::Microsoft.AspNetCore.Routing; +global using global::Microsoft.Extensions.Configuration; +global using global::Microsoft.Extensions.DependencyInjection; +global using global::Microsoft.Extensions.Hosting; +global using global::Microsoft.Extensions.Logging; +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Net.Http.Json; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cache b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cache new file mode 100644 index 0000000..e69de29 diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cs b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cs new file mode 100644 index 0000000..c4c6d21 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cs @@ -0,0 +1,16 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute("Microsoft.AspNetCore.OpenApi")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.assets.cache b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.assets.cache new file mode 100644 index 0000000..f272848 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.assets.cache differ diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.csproj.AssemblyReference.cache b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.csproj.AssemblyReference.cache new file mode 100644 index 0000000..0af13e1 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.csproj.AssemblyReference.cache differ diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.csproj.CoreCompileInputs.cache b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..ef3a490 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +012f82c40fdd58a0afc0fe51f427f6f4d009a0a181b436925f75f6d2b11f7b26 diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.csproj.FileListAbsolute.txt b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..ab05d92 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.csproj.FileListAbsolute.txt @@ -0,0 +1,149 @@ +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\appsettings.Development.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\appsettings.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Api.staticwebassets.endpoints.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Api.exe +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Api.deps.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Api.runtimeconfig.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Api.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Api.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Humanizer.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.AspNetCore.Authentication.JwtBearer.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.AspNetCore.OpenApi.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Bcl.AsyncInterfaces.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Build.Locator.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.CodeAnalysis.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.CodeAnalysis.CSharp.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.CodeAnalysis.CSharp.Workspaces.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Design.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Relational.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Caching.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Caching.Memory.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Configuration.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.DependencyInjection.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.DependencyModel.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Diagnostics.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Diagnostics.HealthChecks.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.FileProviders.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Hosting.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Logging.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Logging.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Options.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.Extensions.Primitives.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.IdentityModel.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.IdentityModel.JsonWebTokens.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.IdentityModel.Logging.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.IdentityModel.Protocols.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.IdentityModel.Tokens.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Microsoft.OpenApi.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Mono.TextTemplating.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Npgsql.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\Npgsql.EntityFrameworkCore.PostgreSQL.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\System.CodeDom.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\System.Composition.AttributedModel.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\System.Composition.Convention.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\System.Composition.Hosting.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\System.Composition.Runtime.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\System.Composition.TypedParts.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\System.IdentityModel.Tokens.Jwt.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\cs\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\de\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\es\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\fr\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\it\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ja\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ko\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\pl\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\pt-BR\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ru\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\tr\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\zh-Hans\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\zh-Hant\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\cs\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\de\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\es\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\fr\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\it\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ja\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ko\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\pl\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\pt-BR\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ru\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\tr\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\zh-Hans\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\zh-Hant\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\cs\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\de\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\es\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\fr\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\it\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ja\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ko\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\pl\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\pt-BR\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ru\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\tr\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\zh-Hans\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\zh-Hant\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\cs\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\de\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\es\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\fr\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\it\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ja\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ko\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\pl\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\pt-BR\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ru\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\tr\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\cs\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\de\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\es\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\fr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\it\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ja\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ko\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\pl\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\pt-BR\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\ru\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\tr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Application.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Infrastructure.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Application.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Infrastructure.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Debug\net9.0\PleasePayMe.Domain.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePayMe.Api.csproj.AssemblyReference.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\rpswa.dswa.cache.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePayMe.Api.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePayMe.Api.AssemblyInfoInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePayMe.Api.AssemblyInfo.cs +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePayMe.Api.csproj.CoreCompileInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cs +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\rjimswa.dswa.cache.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\rjsmrazor.dswa.cache.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\rjsmcshtml.dswa.cache.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\scopedcss\bundle\PleasePayMe.Api.styles.css +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\staticwebassets.build.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\staticwebassets.build.json.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\staticwebassets.development.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\staticwebassets.build.endpoints.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePa.A8AA5E0D.Up2Date +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePayMe.Api.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\refint\PleasePayMe.Api.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePayMe.Api.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\PleasePayMe.Api.genruntimeconfig.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Debug\net9.0\ref\PleasePayMe.Api.dll diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.dll b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.dll new file mode 100644 index 0000000..8aedc80 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.dll differ diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.genruntimeconfig.cache b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.genruntimeconfig.cache new file mode 100644 index 0000000..7800cec --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.genruntimeconfig.cache @@ -0,0 +1 @@ +7b342d177c42f4a953b7b46999c7f668ee536a104e29bf1dc71da47036e44cf9 diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.pdb b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.pdb new file mode 100644 index 0000000..4d14d50 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Debug/net9.0/PleasePayMe.Api.pdb differ diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/apphost.exe b/src/PleasePayMe.Api/obj/Debug/net9.0/apphost.exe new file mode 100644 index 0000000..e418bb7 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Debug/net9.0/apphost.exe differ diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/ref/PleasePayMe.Api.dll b/src/PleasePayMe.Api/obj/Debug/net9.0/ref/PleasePayMe.Api.dll new file mode 100644 index 0000000..dc0e97f Binary files /dev/null and b/src/PleasePayMe.Api/obj/Debug/net9.0/ref/PleasePayMe.Api.dll differ diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/refint/PleasePayMe.Api.dll b/src/PleasePayMe.Api/obj/Debug/net9.0/refint/PleasePayMe.Api.dll new file mode 100644 index 0000000..dc0e97f Binary files /dev/null and b/src/PleasePayMe.Api/obj/Debug/net9.0/refint/PleasePayMe.Api.dll differ diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json b/src/PleasePayMe.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json new file mode 100644 index 0000000..9973a21 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/rjsmcshtml.dswa.cache.json @@ -0,0 +1 @@ +{"GlobalPropertiesHash":"QIQkx21Llj4Fn0H0ETLkrJ64t38DKo4fGqIl8QY/+mQ=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["Vc8AC3msI3lz4jSG3mHYUFns5A25PnqybkRvpgvMsqw=","8Mi4JR1FrTeu95QiZkHRbcxyuRFp7f7o4dncJ4X9qfU=","oYRkifykY47HENoZunao3xTOgLLnxuZEafPEW0GlBlY=","QHXD3\u002B42vOJ9rpfOr4ZVJ74G\u002Bu1c2SgFxc40iOrsvuo=","hZmmO79yIU3YTawXR8OWl3Z67nuGbWIt8S2pLNMxfb8="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json b/src/PleasePayMe.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json new file mode 100644 index 0000000..3c1472c --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/rjsmrazor.dswa.cache.json @@ -0,0 +1 @@ +{"GlobalPropertiesHash":"uSKBosnGj4o3hQN1U7quntz8MhSrdmRfCaJTGXHoOWI=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["Vc8AC3msI3lz4jSG3mHYUFns5A25PnqybkRvpgvMsqw=","8Mi4JR1FrTeu95QiZkHRbcxyuRFp7f7o4dncJ4X9qfU=","oYRkifykY47HENoZunao3xTOgLLnxuZEafPEW0GlBlY=","QHXD3\u002B42vOJ9rpfOr4ZVJ74G\u002Bu1c2SgFxc40iOrsvuo=","hZmmO79yIU3YTawXR8OWl3Z67nuGbWIt8S2pLNMxfb8="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/rpswa.dswa.cache.json b/src/PleasePayMe.Api/obj/Debug/net9.0/rpswa.dswa.cache.json new file mode 100644 index 0000000..b4024ab --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/rpswa.dswa.cache.json @@ -0,0 +1 @@ +{"GlobalPropertiesHash":"HbRfn8vuI2rQ+UGwTb23xAkqZiEA0CO9yIe2PwyAwns=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["Vc8AC3msI3lz4jSG3mHYUFns5A25PnqybkRvpgvMsqw=","8Mi4JR1FrTeu95QiZkHRbcxyuRFp7f7o4dncJ4X9qfU="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/staticwebassets.build.endpoints.json b/src/PleasePayMe.Api/obj/Debug/net9.0/staticwebassets.build.endpoints.json new file mode 100644 index 0000000..5576e88 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/staticwebassets.build.endpoints.json @@ -0,0 +1 @@ +{"Version":1,"ManifestType":"Build","Endpoints":[]} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/staticwebassets.build.json b/src/PleasePayMe.Api/obj/Debug/net9.0/staticwebassets.build.json new file mode 100644 index 0000000..dcbbe8a --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/staticwebassets.build.json @@ -0,0 +1 @@ +{"Version":1,"Hash":"AGQOgGH49LfNGOOG5+NHkHA2SlcMM8Zkr6mEXGhk+Mc=","Source":"PleasePayMe.Api","BasePath":"_content/PleasePayMe.Api","Mode":"Default","ManifestType":"Build","ReferencedProjectsConfiguration":[],"DiscoveryPatterns":[],"Assets":[],"Endpoints":[]} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Debug/net9.0/staticwebassets.build.json.cache b/src/PleasePayMe.Api/obj/Debug/net9.0/staticwebassets.build.json.cache new file mode 100644 index 0000000..2abe8d5 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Debug/net9.0/staticwebassets.build.json.cache @@ -0,0 +1 @@ +AGQOgGH49LfNGOOG5+NHkHA2SlcMM8Zkr6mEXGhk+Mc= \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/PleasePayMe.Api.csproj.nuget.dgspec.json b/src/PleasePayMe.Api/obj/PleasePayMe.Api.csproj.nuget.dgspec.json new file mode 100644 index 0000000..ebfdc19 --- /dev/null +++ b/src/PleasePayMe.Api/obj/PleasePayMe.Api.csproj.nuget.dgspec.json @@ -0,0 +1,297 @@ +{ + "format": 1, + "restore": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Api\\PleasePayMe.Api.csproj": {} + }, + "projects": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Api\\PleasePayMe.Api.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Api\\PleasePayMe.Api.csproj", + "projectName": "PleasePayMe.Api", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Api\\PleasePayMe.Api.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Api\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj" + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "dependencies": { + "Microsoft.AspNetCore.Authentication.JwtBearer": { + "target": "Package", + "version": "[9.0.4, )" + }, + "Microsoft.AspNetCore.OpenApi": { + "target": "Package", + "version": "[9.0.17, )" + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[9.0.4, )" + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": { + "target": "Package", + "version": "[9.0.4, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj", + "projectName": "PleasePayMe.Application", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "projectName": "PleasePayMe.Domain", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj", + "projectName": "PleasePayMe.Infrastructure", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj" + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "dependencies": { + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[9.0.4, )" + }, + "Npgsql.EntityFrameworkCore.PostgreSQL": { + "target": "Package", + "version": "[9.0.4, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/PleasePayMe.Api.csproj.nuget.g.props b/src/PleasePayMe.Api/obj/PleasePayMe.Api.csproj.nuget.g.props new file mode 100644 index 0000000..49de9eb --- /dev/null +++ b/src/PleasePayMe.Api/obj/PleasePayMe.Api.csproj.nuget.g.props @@ -0,0 +1,23 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget + C:\Users\ggpo1\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget + PackageReference + 6.14.3 + + + + + + + + + + + C:\Users\ggpo1\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget\microsoft.codeanalysis.analyzers\3.3.4 + + \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/PleasePayMe.Api.csproj.nuget.g.targets b/src/PleasePayMe.Api/obj/PleasePayMe.Api.csproj.nuget.g.targets new file mode 100644 index 0000000..6c9ca84 --- /dev/null +++ b/src/PleasePayMe.Api/obj/PleasePayMe.Api.csproj.nuget.g.targets @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/src/PleasePayMe.Api/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePa.A8AA5E0D.Up2Date b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePa.A8AA5E0D.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.AssemblyInfo.cs b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.AssemblyInfo.cs new file mode 100644 index 0000000..ec84be9 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("PleasePayMe.Api")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("PleasePayMe.Api")] +[assembly: System.Reflection.AssemblyTitleAttribute("PleasePayMe.Api")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.AssemblyInfoInputs.cache b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.AssemblyInfoInputs.cache new file mode 100644 index 0000000..c306841 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +5675b0b505ffd82434e69cf2d38f0ac764f955f03a6b989698e0680e6a516de7 diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.GeneratedMSBuildEditorConfig.editorconfig b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..c2f2a6f --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,29 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = true +build_property.UsingMicrosoftNETSdkWeb = true +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = PleasePayMe.Api +build_property.RootNamespace = PleasePayMe.Api +build_property.ProjectDir = C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.RazorLangVersion = 9.0 +build_property.SupportLocalizedComponentNames = +build_property.GenerateRazorMetadataSourceChecksumAttributes = +build_property.MSBuildProjectDirectory = C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api +build_property._RazorSourceGeneratorDebug = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.GlobalUsings.g.cs b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.GlobalUsings.g.cs new file mode 100644 index 0000000..025530a --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.GlobalUsings.g.cs @@ -0,0 +1,17 @@ +// +global using global::Microsoft.AspNetCore.Builder; +global using global::Microsoft.AspNetCore.Hosting; +global using global::Microsoft.AspNetCore.Http; +global using global::Microsoft.AspNetCore.Routing; +global using global::Microsoft.Extensions.Configuration; +global using global::Microsoft.Extensions.DependencyInjection; +global using global::Microsoft.Extensions.Hosting; +global using global::Microsoft.Extensions.Logging; +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Net.Http.Json; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cache b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cache new file mode 100644 index 0000000..e69de29 diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cs b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cs new file mode 100644 index 0000000..c4c6d21 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cs @@ -0,0 +1,16 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute("Microsoft.AspNetCore.OpenApi")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.assets.cache b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.assets.cache new file mode 100644 index 0000000..6178457 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.assets.cache differ diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.csproj.AssemblyReference.cache b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.csproj.AssemblyReference.cache new file mode 100644 index 0000000..0b84239 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.csproj.AssemblyReference.cache differ diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.csproj.CoreCompileInputs.cache b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..b836655 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +36e52922eaa349cf3a824c9d6b04f213bed7f864573c2cf7996a897add60bef1 diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.csproj.FileListAbsolute.txt b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..325e13b --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.csproj.FileListAbsolute.txt @@ -0,0 +1,149 @@ +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\appsettings.Development.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\appsettings.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Api.staticwebassets.endpoints.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Api.exe +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Api.deps.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Api.runtimeconfig.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Api.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Api.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Humanizer.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.AspNetCore.Authentication.JwtBearer.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.AspNetCore.OpenApi.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Bcl.AsyncInterfaces.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Build.Locator.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.CodeAnalysis.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.CodeAnalysis.CSharp.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.CodeAnalysis.CSharp.Workspaces.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.CodeAnalysis.Workspaces.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.EntityFrameworkCore.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.EntityFrameworkCore.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.EntityFrameworkCore.Design.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.EntityFrameworkCore.Relational.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Caching.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Caching.Memory.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Configuration.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.DependencyInjection.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.DependencyModel.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Diagnostics.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Diagnostics.HealthChecks.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.FileProviders.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Hosting.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Logging.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Logging.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Options.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.Extensions.Primitives.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.IdentityModel.Abstractions.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.IdentityModel.JsonWebTokens.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.IdentityModel.Logging.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.IdentityModel.Protocols.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.IdentityModel.Tokens.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Microsoft.OpenApi.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Mono.TextTemplating.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Npgsql.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\Npgsql.EntityFrameworkCore.PostgreSQL.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\System.CodeDom.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\System.Composition.AttributedModel.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\System.Composition.Convention.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\System.Composition.Hosting.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\System.Composition.Runtime.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\System.Composition.TypedParts.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\System.IdentityModel.Tokens.Jwt.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\cs\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\de\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\es\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\fr\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\it\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ja\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ko\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\pl\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\pt-BR\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ru\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\tr\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\zh-Hans\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\zh-Hant\Microsoft.CodeAnalysis.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\cs\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\de\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\es\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\fr\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\it\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ja\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ko\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\pl\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\pt-BR\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ru\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\tr\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\zh-Hans\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\zh-Hant\Microsoft.CodeAnalysis.CSharp.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\cs\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\de\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\es\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\fr\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\it\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ja\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ko\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\pl\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\pt-BR\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ru\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\tr\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\zh-Hans\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\zh-Hant\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\cs\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\de\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\es\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\fr\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\it\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ja\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ko\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\pl\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\pt-BR\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ru\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\tr\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\cs\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\de\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\es\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\fr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\it\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ja\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ko\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\pl\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\pt-BR\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\ru\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\tr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Application.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Infrastructure.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Application.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Infrastructure.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\bin\Release\net9.0\PleasePayMe.Domain.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePayMe.Api.csproj.AssemblyReference.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\rpswa.dswa.cache.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePayMe.Api.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePayMe.Api.AssemblyInfoInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePayMe.Api.AssemblyInfo.cs +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePayMe.Api.csproj.CoreCompileInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cs +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePayMe.Api.MvcApplicationPartsAssemblyInfo.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\rjimswa.dswa.cache.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\rjsmrazor.dswa.cache.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\rjsmcshtml.dswa.cache.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\scopedcss\bundle\PleasePayMe.Api.styles.css +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\staticwebassets.build.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\staticwebassets.build.json.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\staticwebassets.development.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\staticwebassets.build.endpoints.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePa.A8AA5E0D.Up2Date +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePayMe.Api.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\refint\PleasePayMe.Api.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePayMe.Api.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\PleasePayMe.Api.genruntimeconfig.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Api\obj\Release\net9.0\ref\PleasePayMe.Api.dll diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.dll b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.dll new file mode 100644 index 0000000..db4b066 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.dll differ diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.genruntimeconfig.cache b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.genruntimeconfig.cache new file mode 100644 index 0000000..d22eea6 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.genruntimeconfig.cache @@ -0,0 +1 @@ +d2231d99670d6669c1cdaff2af4598109784e39111b8941843ab5921e7b09aed diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.pdb b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.pdb new file mode 100644 index 0000000..3ac9cab Binary files /dev/null and b/src/PleasePayMe.Api/obj/Release/net9.0/PleasePayMe.Api.pdb differ diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/apphost.exe b/src/PleasePayMe.Api/obj/Release/net9.0/apphost.exe new file mode 100644 index 0000000..e418bb7 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Release/net9.0/apphost.exe differ diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/ref/PleasePayMe.Api.dll b/src/PleasePayMe.Api/obj/Release/net9.0/ref/PleasePayMe.Api.dll new file mode 100644 index 0000000..368fdf0 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Release/net9.0/ref/PleasePayMe.Api.dll differ diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/refint/PleasePayMe.Api.dll b/src/PleasePayMe.Api/obj/Release/net9.0/refint/PleasePayMe.Api.dll new file mode 100644 index 0000000..368fdf0 Binary files /dev/null and b/src/PleasePayMe.Api/obj/Release/net9.0/refint/PleasePayMe.Api.dll differ diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/rjsmcshtml.dswa.cache.json b/src/PleasePayMe.Api/obj/Release/net9.0/rjsmcshtml.dswa.cache.json new file mode 100644 index 0000000..1149e27 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/rjsmcshtml.dswa.cache.json @@ -0,0 +1 @@ +{"GlobalPropertiesHash":"z5bkl0PawcM190Y2kTV8GTTqDgAHmb+2o+egNO0PCDc=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["A6FV62ZBYaFjG1CmFCjicGj5J1KoSXtaPkw88Prjl4w=","lw3q6lr14Dc\u002BTrAwCQgQVHfT05qs\u002BIcPiLANFoboUhk=","e\u002B5kiTvRZXdfjGxMAtgA8XS7bKh\u002B3A6C8bKl1E6zlcI=","evK\u002B77NAf1NuyL9wsLBr7UgR9ioDrqy6Urr1fymHzm0=","8btoz9bY90kmvSXp0z/4kTqpHERTd5AIWDVQ9ieXdEg="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/rjsmrazor.dswa.cache.json b/src/PleasePayMe.Api/obj/Release/net9.0/rjsmrazor.dswa.cache.json new file mode 100644 index 0000000..76fe570 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/rjsmrazor.dswa.cache.json @@ -0,0 +1 @@ +{"GlobalPropertiesHash":"K1Ds43ZdxFTTBWsTzq00pxUHK4G9D+JRdTSFDK1aWXM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["A6FV62ZBYaFjG1CmFCjicGj5J1KoSXtaPkw88Prjl4w=","lw3q6lr14Dc\u002BTrAwCQgQVHfT05qs\u002BIcPiLANFoboUhk=","e\u002B5kiTvRZXdfjGxMAtgA8XS7bKh\u002B3A6C8bKl1E6zlcI=","evK\u002B77NAf1NuyL9wsLBr7UgR9ioDrqy6Urr1fymHzm0=","8btoz9bY90kmvSXp0z/4kTqpHERTd5AIWDVQ9ieXdEg="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/rpswa.dswa.cache.json b/src/PleasePayMe.Api/obj/Release/net9.0/rpswa.dswa.cache.json new file mode 100644 index 0000000..b485214 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/rpswa.dswa.cache.json @@ -0,0 +1 @@ +{"GlobalPropertiesHash":"d9zi+5HHC7+aomyme5GiJX8L+ajdtx8YgCxRObsMfJs=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["A6FV62ZBYaFjG1CmFCjicGj5J1KoSXtaPkw88Prjl4w=","lw3q6lr14Dc\u002BTrAwCQgQVHfT05qs\u002BIcPiLANFoboUhk="],"CachedAssets":{},"CachedCopyCandidates":{}} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/staticwebassets.build.endpoints.json b/src/PleasePayMe.Api/obj/Release/net9.0/staticwebassets.build.endpoints.json new file mode 100644 index 0000000..5576e88 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/staticwebassets.build.endpoints.json @@ -0,0 +1 @@ +{"Version":1,"ManifestType":"Build","Endpoints":[]} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/staticwebassets.build.json b/src/PleasePayMe.Api/obj/Release/net9.0/staticwebassets.build.json new file mode 100644 index 0000000..dcbbe8a --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/staticwebassets.build.json @@ -0,0 +1 @@ +{"Version":1,"Hash":"AGQOgGH49LfNGOOG5+NHkHA2SlcMM8Zkr6mEXGhk+Mc=","Source":"PleasePayMe.Api","BasePath":"_content/PleasePayMe.Api","Mode":"Default","ManifestType":"Build","ReferencedProjectsConfiguration":[],"DiscoveryPatterns":[],"Assets":[],"Endpoints":[]} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/Release/net9.0/staticwebassets.build.json.cache b/src/PleasePayMe.Api/obj/Release/net9.0/staticwebassets.build.json.cache new file mode 100644 index 0000000..2abe8d5 --- /dev/null +++ b/src/PleasePayMe.Api/obj/Release/net9.0/staticwebassets.build.json.cache @@ -0,0 +1 @@ +AGQOgGH49LfNGOOG5+NHkHA2SlcMM8Zkr6mEXGhk+Mc= \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/project.assets.json b/src/PleasePayMe.Api/obj/project.assets.json new file mode 100644 index 0000000..6235274 --- /dev/null +++ b/src/PleasePayMe.Api/obj/project.assets.json @@ -0,0 +1,3553 @@ +{ + "version": 3, + "targets": { + "net9.0": { + "Humanizer.Core/2.14.1": { + "type": "package", + "compile": { + "lib/net6.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/Humanizer.dll": { + "related": ".xml" + } + } + }, + "Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.0.1" + }, + "compile": { + "lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": { + "related": ".xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Microsoft.AspNetCore.OpenApi/9.0.17": { + "type": "package", + "dependencies": { + "Microsoft.OpenApi": "1.6.17" + }, + "compile": { + "lib/net9.0/Microsoft.AspNetCore.OpenApi.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.AspNetCore.OpenApi.dll": { + "related": ".xml" + } + }, + "frameworkReferences": [ + "Microsoft.AspNetCore.App" + ] + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "type": "package", + "compile": { + "lib/netstandard2.1/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Build.Framework/17.8.3": { + "type": "package", + "compile": { + "ref/net8.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/_._": { + "related": ".pdb;.xml" + } + } + }, + "Microsoft.Build.Locator/1.7.8": { + "type": "package", + "compile": { + "lib/net6.0/_._": {} + }, + "runtime": { + "lib/net6.0/Microsoft.Build.Locator.dll": {} + }, + "build": { + "build/_._": {} + } + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": { + "type": "package", + "build": { + "buildTransitive/Microsoft.CodeAnalysis.Analyzers.props": {}, + "buildTransitive/Microsoft.CodeAnalysis.Analyzers.targets": {} + } + }, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "type": "package", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.4", + "System.Collections.Immutable": "7.0.0", + "System.Reflection.Metadata": "7.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.dll": { + "related": ".pdb;.xml" + } + }, + "resource": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "type": "package", + "dependencies": { + "Microsoft.CodeAnalysis.Common": "[4.8.0]" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.dll": { + "related": ".pdb;.xml" + } + }, + "resource": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "type": "package", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.CodeAnalysis.CSharp": "[4.8.0]", + "Microsoft.CodeAnalysis.Common": "[4.8.0]", + "Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll": { + "related": ".pdb;.xml" + } + }, + "resource": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "type": "package", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Bcl.AsyncInterfaces": "7.0.0", + "Microsoft.CodeAnalysis.Common": "[4.8.0]", + "System.Composition": "7.0.0", + "System.IO.Pipelines": "7.0.0", + "System.Threading.Channels": "7.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.dll": { + "related": ".pdb;.xml" + } + }, + "resource": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "type": "package", + "dependencies": { + "Microsoft.Build.Framework": "16.10.0", + "Microsoft.CodeAnalysis.Common": "[4.8.0]", + "Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]", + "System.Text.Json": "7.0.3" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".pdb;.runtimeconfig.json;.xml" + } + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll": { + "related": ".pdb;.runtimeconfig.json;.xml" + }, + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll": { + "related": ".BuildHost.pdb;.BuildHost.runtimeconfig.json;.BuildHost.xml;.pdb;.xml" + } + }, + "resource": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "9.0.4", + "Microsoft.EntityFrameworkCore.Analyzers": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "compile": { + "lib/net8.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.EntityFrameworkCore.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "type": "package", + "compile": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": { + "type": "package" + }, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "type": "package", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.Build.Locator": "1.7.8", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.MSBuild": "4.8.0", + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyModel": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4", + "Mono.TextTemplating": "3.0.0", + "System.Text.Json": "9.0.4" + }, + "compile": { + "lib/net8.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Design.dll": { + "related": ".xml" + } + }, + "build": { + "build/net8.0/Microsoft.EntityFrameworkCore.Design.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "compile": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "type": "package", + "compile": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "type": "package", + "compile": { + "lib/net9.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "9.0.4", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/9.0.4": { + "type": "package", + "compile": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Microsoft.Extensions.Diagnostics.HealthChecks": "9.0.4", + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.FileProviders.Abstractions/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Hosting.Abstractions/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.4", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets": {} + } + }, + "Microsoft.Extensions.Options/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets": {} + } + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "type": "package", + "compile": { + "lib/net9.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.IdentityModel.Abstractions/8.0.1": { + "type": "package", + "compile": { + "lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.IdentityModel.JsonWebTokens/8.0.1": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.0.1" + }, + "compile": { + "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": { + "related": ".xml" + } + } + }, + "Microsoft.IdentityModel.Logging/8.0.1": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "8.0.1" + }, + "compile": { + "lib/net9.0/Microsoft.IdentityModel.Logging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Logging.dll": { + "related": ".xml" + } + } + }, + "Microsoft.IdentityModel.Protocols/8.0.1": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.0.1" + }, + "compile": { + "lib/net9.0/Microsoft.IdentityModel.Protocols.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Protocols.dll": { + "related": ".xml" + } + } + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.Protocols": "8.0.1", + "System.IdentityModel.Tokens.Jwt": "8.0.1" + }, + "compile": { + "lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": { + "related": ".xml" + } + } + }, + "Microsoft.IdentityModel.Tokens/8.0.1": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.Logging": "8.0.1" + }, + "compile": { + "lib/net9.0/Microsoft.IdentityModel.Tokens.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.IdentityModel.Tokens.dll": { + "related": ".xml" + } + } + }, + "Microsoft.OpenApi/1.6.17": { + "type": "package", + "compile": { + "lib/netstandard2.0/Microsoft.OpenApi.dll": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/netstandard2.0/Microsoft.OpenApi.dll": { + "related": ".pdb;.xml" + } + } + }, + "Mono.TextTemplating/3.0.0": { + "type": "package", + "dependencies": { + "System.CodeDom": "6.0.0" + }, + "compile": { + "lib/net6.0/_._": {} + }, + "runtime": { + "lib/net6.0/Mono.TextTemplating.dll": {} + }, + "build": { + "buildTransitive/Mono.TextTemplating.targets": {} + } + }, + "Npgsql/9.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "8.0.2" + }, + "compile": { + "lib/net8.0/Npgsql.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Npgsql.dll": { + "related": ".xml" + } + } + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "[9.0.1, 10.0.0)", + "Microsoft.EntityFrameworkCore.Relational": "[9.0.1, 10.0.0)", + "Npgsql": "9.0.3" + }, + "compile": { + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll": { + "related": ".xml" + } + } + }, + "System.CodeDom/6.0.0": { + "type": "package", + "compile": { + "lib/net6.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/System.CodeDom.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/netcoreapp3.1/_._": {} + } + }, + "System.Collections.Immutable/7.0.0": { + "type": "package", + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Collections.Immutable.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition/7.0.0": { + "type": "package", + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Convention": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0", + "System.Composition.TypedParts": "7.0.0" + }, + "compile": { + "lib/netcoreapp2.0/_._": {} + }, + "runtime": { + "lib/netcoreapp2.0/_._": {} + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition.AttributedModel/7.0.0": { + "type": "package", + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Composition.AttributedModel.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition.Convention/7.0.0": { + "type": "package", + "dependencies": { + "System.Composition.AttributedModel": "7.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Composition.Convention.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition.Hosting/7.0.0": { + "type": "package", + "dependencies": { + "System.Composition.Runtime": "7.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Composition.Hosting.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition.Runtime/7.0.0": { + "type": "package", + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Composition.Runtime.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition.TypedParts/7.0.0": { + "type": "package", + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Composition.TypedParts.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.IdentityModel.Tokens.Jwt/8.0.1": { + "type": "package", + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "8.0.1", + "Microsoft.IdentityModel.Tokens": "8.0.1" + }, + "compile": { + "lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": { + "related": ".xml" + } + } + }, + "System.IO.Pipelines/7.0.0": { + "type": "package", + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.IO.Pipelines.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Reflection.Metadata/7.0.0": { + "type": "package", + "dependencies": { + "System.Collections.Immutable": "7.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Reflection.Metadata.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "type": "package", + "compile": { + "lib/net6.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/netcoreapp3.1/_._": {} + } + }, + "System.Text.Json/9.0.4": { + "type": "package", + "compile": { + "lib/net9.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/System.Text.Json.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/System.Text.Json.targets": {} + } + }, + "System.Threading.Channels/7.0.0": { + "type": "package", + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Threading.Channels.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "PleasePayMe.Application/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v9.0", + "dependencies": { + "PleasePayMe.Domain": "1.0.0" + }, + "compile": { + "bin/placeholder/PleasePayMe.Application.dll": {} + }, + "runtime": { + "bin/placeholder/PleasePayMe.Application.dll": {} + } + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v9.0", + "compile": { + "bin/placeholder/PleasePayMe.Domain.dll": {} + }, + "runtime": { + "bin/placeholder/PleasePayMe.Domain.dll": {} + } + }, + "PleasePayMe.Infrastructure/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v9.0", + "dependencies": { + "Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.4", + "PleasePayMe.Application": "1.0.0", + "PleasePayMe.Domain": "1.0.0" + }, + "compile": { + "bin/placeholder/PleasePayMe.Infrastructure.dll": {} + }, + "runtime": { + "bin/placeholder/PleasePayMe.Infrastructure.dll": {} + } + } + } + }, + "libraries": { + "Humanizer.Core/2.14.1": { + "sha512": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==", + "type": "package", + "path": "humanizer.core/2.14.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "humanizer.core.2.14.1.nupkg.sha512", + "humanizer.core.nuspec", + "lib/net6.0/Humanizer.dll", + "lib/net6.0/Humanizer.xml", + "lib/netstandard1.0/Humanizer.dll", + "lib/netstandard1.0/Humanizer.xml", + "lib/netstandard2.0/Humanizer.dll", + "lib/netstandard2.0/Humanizer.xml", + "logo.png" + ] + }, + "Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": { + "sha512": "0HgfWPfnjlzWFbW4pw6FYNuIMV8obVU+MUkiZ33g4UOpvZcmdWzdayfheKPZ5+EUly8SvfgW0dJwwIrW4IVLZQ==", + "type": "package", + "path": "microsoft.aspnetcore.authentication.jwtbearer/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll", + "lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.xml", + "microsoft.aspnetcore.authentication.jwtbearer.9.0.4.nupkg.sha512", + "microsoft.aspnetcore.authentication.jwtbearer.nuspec" + ] + }, + "Microsoft.AspNetCore.OpenApi/9.0.17": { + "sha512": "+zcqQ/JecNl4G1hC2mrJ8qDolJv17W3grToEqcGZGqa3cXWaCjA9KTdigU0WVK3LWI0TtOG/Q/joXRdKqFhB9Q==", + "type": "package", + "path": "microsoft.aspnetcore.openapi/9.0.17", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "lib/net9.0/Microsoft.AspNetCore.OpenApi.dll", + "lib/net9.0/Microsoft.AspNetCore.OpenApi.xml", + "microsoft.aspnetcore.openapi.9.0.17.nupkg.sha512", + "microsoft.aspnetcore.openapi.nuspec" + ] + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "sha512": "3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg==", + "type": "package", + "path": "microsoft.bcl.asyncinterfaces/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Bcl.AsyncInterfaces.targets", + "buildTransitive/net462/_._", + "lib/net462/Microsoft.Bcl.AsyncInterfaces.dll", + "lib/net462/Microsoft.Bcl.AsyncInterfaces.xml", + "lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll", + "lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.xml", + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll", + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.xml", + "microsoft.bcl.asyncinterfaces.7.0.0.nupkg.sha512", + "microsoft.bcl.asyncinterfaces.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Build.Framework/17.8.3": { + "sha512": "NrQZJW8TlKVPx72yltGb8SVz3P5mNRk9fNiD/ao8jRSk48WqIIdCn99q4IjlVmPcruuQ+yLdjNQLL8Rb4c916g==", + "type": "package", + "path": "microsoft.build.framework/17.8.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "MSBuild-NuGet-Icon.png", + "README.md", + "lib/net472/Microsoft.Build.Framework.dll", + "lib/net472/Microsoft.Build.Framework.pdb", + "lib/net472/Microsoft.Build.Framework.xml", + "lib/net8.0/Microsoft.Build.Framework.dll", + "lib/net8.0/Microsoft.Build.Framework.pdb", + "lib/net8.0/Microsoft.Build.Framework.xml", + "microsoft.build.framework.17.8.3.nupkg.sha512", + "microsoft.build.framework.nuspec", + "notices/THIRDPARTYNOTICES.txt", + "ref/net472/Microsoft.Build.Framework.dll", + "ref/net472/Microsoft.Build.Framework.xml", + "ref/net8.0/Microsoft.Build.Framework.dll", + "ref/net8.0/Microsoft.Build.Framework.xml", + "ref/netstandard2.0/Microsoft.Build.Framework.dll", + "ref/netstandard2.0/Microsoft.Build.Framework.xml" + ] + }, + "Microsoft.Build.Locator/1.7.8": { + "sha512": "sPy10x527Ph16S2u0yGME4S6ohBKJ69WfjeGG/bvELYeZVmJdKjxgnlL8cJJJLGV/cZIRqSfB12UDB8ICakOog==", + "type": "package", + "path": "microsoft.build.locator/1.7.8", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "MSBuild-NuGet-Icon.png", + "build/Microsoft.Build.Locator.props", + "build/Microsoft.Build.Locator.targets", + "lib/net46/Microsoft.Build.Locator.dll", + "lib/net6.0/Microsoft.Build.Locator.dll", + "microsoft.build.locator.1.7.8.nupkg.sha512", + "microsoft.build.locator.nuspec" + ] + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": { + "sha512": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==", + "type": "package", + "path": "microsoft.codeanalysis.analyzers/3.3.4", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.Analyzers.dll", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.Analyzers.dll", + "analyzers/dotnet/cs/cs/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.Analyzers.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.VisualBasic.Analyzers.dll", + "analyzers/dotnet/vb/cs/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/de/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/es/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/fr/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/it/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/ja/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/ko/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/pl/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/pt-BR/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/ru/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/tr/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hans/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hant/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "buildTransitive/Microsoft.CodeAnalysis.Analyzers.props", + "buildTransitive/Microsoft.CodeAnalysis.Analyzers.targets", + "buildTransitive/config/analysislevel_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_all.globalconfig", + "buildTransitive/config/analysislevel_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_default.globalconfig", + "buildTransitive/config/analysislevel_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevel_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_none.globalconfig", + "buildTransitive/config/analysislevel_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevel_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_all.globalconfig", + "buildTransitive/config/analysislevel_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_default.globalconfig", + "buildTransitive/config/analysislevel_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_minimum.globalconfig", + "buildTransitive/config/analysislevel_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_none.globalconfig", + "buildTransitive/config/analysislevel_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_recommended.globalconfig", + "buildTransitive/config/analysislevel_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_4_3_all.globalconfig", + "buildTransitive/config/analysislevel_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_4_3_default.globalconfig", + "buildTransitive/config/analysislevel_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevel_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_4_3_none.globalconfig", + "buildTransitive/config/analysislevel_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevel_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_all.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_default.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_none.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_all.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_default.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_minimum.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_none.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_recommended.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_all.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_default.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_none.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_all.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_default.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_none.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_all.globalconfig", + "buildTransitive/config/analysislevellibrary_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_default.globalconfig", + "buildTransitive/config/analysislevellibrary_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_minimum.globalconfig", + "buildTransitive/config/analysislevellibrary_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_none.globalconfig", + "buildTransitive/config/analysislevellibrary_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_recommended.globalconfig", + "buildTransitive/config/analysislevellibrary_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_all.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_default.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_none.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_recommended_warnaserror.globalconfig", + "documentation/Analyzer Configuration.md", + "documentation/Microsoft.CodeAnalysis.Analyzers.md", + "documentation/Microsoft.CodeAnalysis.Analyzers.sarif", + "editorconfig/AllRulesDefault/.editorconfig", + "editorconfig/AllRulesDisabled/.editorconfig", + "editorconfig/AllRulesEnabled/.editorconfig", + "editorconfig/CorrectnessRulesDefault/.editorconfig", + "editorconfig/CorrectnessRulesEnabled/.editorconfig", + "editorconfig/DataflowRulesDefault/.editorconfig", + "editorconfig/DataflowRulesEnabled/.editorconfig", + "editorconfig/LibraryRulesDefault/.editorconfig", + "editorconfig/LibraryRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisCompatibilityRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisCompatibilityRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisCorrectnessRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisCorrectnessRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisDesignRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisDesignRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisDocumentationRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisDocumentationRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisLocalizationRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisLocalizationRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisPerformanceRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisPerformanceRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisReleaseTrackingRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisReleaseTrackingRulesEnabled/.editorconfig", + "editorconfig/PortedFromFxCopRulesDefault/.editorconfig", + "editorconfig/PortedFromFxCopRulesEnabled/.editorconfig", + "microsoft.codeanalysis.analyzers.3.3.4.nupkg.sha512", + "microsoft.codeanalysis.analyzers.nuspec", + "rulesets/AllRulesDefault.ruleset", + "rulesets/AllRulesDisabled.ruleset", + "rulesets/AllRulesEnabled.ruleset", + "rulesets/CorrectnessRulesDefault.ruleset", + "rulesets/CorrectnessRulesEnabled.ruleset", + "rulesets/DataflowRulesDefault.ruleset", + "rulesets/DataflowRulesEnabled.ruleset", + "rulesets/LibraryRulesDefault.ruleset", + "rulesets/LibraryRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisCompatibilityRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisCompatibilityRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisCorrectnessRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisCorrectnessRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisDesignRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisDesignRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisDocumentationRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisDocumentationRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisLocalizationRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisLocalizationRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisPerformanceRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisPerformanceRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisReleaseTrackingRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisReleaseTrackingRulesEnabled.ruleset", + "rulesets/PortedFromFxCopRulesDefault.ruleset", + "rulesets/PortedFromFxCopRulesEnabled.ruleset", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "sha512": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==", + "type": "package", + "path": "microsoft.codeanalysis.common/4.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.rtf", + "lib/net6.0/Microsoft.CodeAnalysis.dll", + "lib/net6.0/Microsoft.CodeAnalysis.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.xml", + "lib/net6.0/cs/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/de/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/es/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/fr/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/it/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/ja/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/ko/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/pl/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/pt-BR/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/ru/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/tr/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/Microsoft.CodeAnalysis.dll", + "lib/net7.0/Microsoft.CodeAnalysis.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.xml", + "lib/net7.0/cs/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/de/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/es/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/fr/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/it/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/ja/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/ko/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/pl/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/ru/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/tr/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.pdb", + "lib/netstandard2.0/Microsoft.CodeAnalysis.xml", + "lib/netstandard2.0/cs/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/de/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/es/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/fr/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/it/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/ja/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/ko/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/pl/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/ru/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/tr/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll", + "microsoft.codeanalysis.common.4.8.0.nupkg.sha512", + "microsoft.codeanalysis.common.nuspec" + ] + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "sha512": "+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==", + "type": "package", + "path": "microsoft.codeanalysis.csharp/4.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.rtf", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.dll", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.xml", + "lib/net6.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.dll", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.xml", + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.pdb", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.xml", + "lib/netstandard2.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll", + "microsoft.codeanalysis.csharp.4.8.0.nupkg.sha512", + "microsoft.codeanalysis.csharp.nuspec" + ] + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "sha512": "3amm4tq4Lo8/BGvg9p3BJh3S9nKq2wqCXfS7138i69TUpo/bD+XvD0hNurpEBtcNZhi1FyutiomKJqVF39ugYA==", + "type": "package", + "path": "microsoft.codeanalysis.csharp.workspaces/4.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.rtf", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.Workspaces.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.Workspaces.xml", + "lib/net6.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.xml", + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.Workspaces.pdb", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.Workspaces.xml", + "lib/netstandard2.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "microsoft.codeanalysis.csharp.workspaces.4.8.0.nupkg.sha512", + "microsoft.codeanalysis.csharp.workspaces.nuspec" + ] + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "sha512": "LXyV+MJKsKRu3FGJA3OmSk40OUIa/dQCFLOnm5X8MNcujx7hzGu8o+zjXlb/cy5xUdZK2UKYb9YaQ2E8m9QehQ==", + "type": "package", + "path": "microsoft.codeanalysis.workspaces.common/4.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.rtf", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.dll", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.xml", + "lib/net6.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.dll", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.xml", + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.Workspaces.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.Workspaces.pdb", + "lib/netstandard2.0/Microsoft.CodeAnalysis.Workspaces.xml", + "lib/netstandard2.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "microsoft.codeanalysis.workspaces.common.4.8.0.nupkg.sha512", + "microsoft.codeanalysis.workspaces.common.nuspec" + ] + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "sha512": "IEYreI82QZKklp54yPHxZNG9EKSK6nHEkeuf+0Asie9llgS1gp0V1hw7ODG+QyoB7MuAnNQHmeV1Per/ECpv6A==", + "type": "package", + "path": "microsoft.codeanalysis.workspaces.msbuild/4.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.rtf", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.pdb", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.xml", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.pdb", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.xml", + "lib/net472/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.runtimeconfig.json", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.xml", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.xml", + "lib/net6.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.runtimeconfig.json", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.xml", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.xml", + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "microsoft.codeanalysis.workspaces.msbuild.4.8.0.nupkg.sha512", + "microsoft.codeanalysis.workspaces.msbuild.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "sha512": "+5IAX0aicQYCRfN4pAjad+JPwdEYoVEM3Z1Cl8/EiEv3FVHQHdd8TJQpQIslQDDQS/UsUMb0MsOXwqOh+TJtRw==", + "type": "package", + "path": "microsoft.entityframeworkcore/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "buildTransitive/net8.0/Microsoft.EntityFrameworkCore.props", + "lib/net8.0/Microsoft.EntityFrameworkCore.dll", + "lib/net8.0/Microsoft.EntityFrameworkCore.xml", + "microsoft.entityframeworkcore.9.0.4.nupkg.sha512", + "microsoft.entityframeworkcore.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "sha512": "E0pkWzI0liqu2ogqJ1kohk2eGkYRhf5tI75HGF6IQDARsshY/0w+prGyLvNuUeV7B8I7vYQZ4CzAKYKxw7b9gQ==", + "type": "package", + "path": "microsoft.entityframeworkcore.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll", + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.xml", + "microsoft.entityframeworkcore.abstractions.9.0.4.nupkg.sha512", + "microsoft.entityframeworkcore.abstractions.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": { + "sha512": "cMsm1O7g9X5qbB2wjHf3BVVvGwkG+zeXQ+M91I1Bm6RfylFMImqBPzs0+vmuef7fPxr2yOzPhIfJ2wQJfmtaSw==", + "type": "package", + "path": "microsoft.entityframeworkcore.analyzers/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll", + "docs/PACKAGE.md", + "microsoft.entityframeworkcore.analyzers.9.0.4.nupkg.sha512", + "microsoft.entityframeworkcore.analyzers.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "sha512": "0NdtmsbYfMr2HyF+W6L+kPaHJl1nAmFjWj0MfI5G+CFeWZxDwltQxzzwSmZQ4QhS5z8zjczGXwHZ8e3iFaoiXA==", + "type": "package", + "path": "microsoft.entityframeworkcore.design/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "build/net8.0/Microsoft.EntityFrameworkCore.Design.props", + "lib/net8.0/Microsoft.EntityFrameworkCore.Design.dll", + "lib/net8.0/Microsoft.EntityFrameworkCore.Design.xml", + "microsoft.entityframeworkcore.design.9.0.4.nupkg.sha512", + "microsoft.entityframeworkcore.design.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "sha512": "OjJ+xh/wQff5b0wiC3SPvoQqTA2boZeJQf+15+3+OJPtjBKzvxuwr25QRIu1p1t+K8ryQ8pzaoZ7eOpXfNzVGA==", + "type": "package", + "path": "microsoft.entityframeworkcore.relational/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll", + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.xml", + "microsoft.entityframeworkcore.relational.9.0.4.nupkg.sha512", + "microsoft.entityframeworkcore.relational.nuspec" + ] + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "sha512": "imcZ5BGhBw5mNsWLepBbqqumWaFe0GtvyCvne2/2wsDIBRa2+Lhx4cU/pKt/4BwOizzUEOls2k1eOJQXHGMalg==", + "type": "package", + "path": "microsoft.extensions.caching.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Abstractions.targets", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.xml", + "microsoft.extensions.caching.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.caching.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "sha512": "G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==", + "type": "package", + "path": "microsoft.extensions.caching.memory/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Memory.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Memory.targets", + "lib/net462/Microsoft.Extensions.Caching.Memory.dll", + "lib/net462/Microsoft.Extensions.Caching.Memory.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.xml", + "microsoft.extensions.caching.memory.9.0.4.nupkg.sha512", + "microsoft.extensions.caching.memory.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "sha512": "0LN/DiIKvBrkqp7gkF3qhGIeZk6/B63PthAHjQsxymJfIBcz0kbf4/p/t4lMgggVxZ+flRi5xvTwlpPOoZk8fg==", + "type": "package", + "path": "microsoft.extensions.configuration.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.Abstractions.targets", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "microsoft.extensions.configuration.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.configuration.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "sha512": "f2MTUaS2EQ3lX4325ytPAISZqgBfXmY0WvgD80ji6Z20AoDNiCESxsqo6mFRwHJD/jfVKRw9FsW6+86gNre3ug==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.targets", + "lib/net462/Microsoft.Extensions.DependencyInjection.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.xml", + "microsoft.extensions.dependencyinjection.9.0.4.nupkg.sha512", + "microsoft.extensions.dependencyinjection.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "sha512": "UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "microsoft.extensions.dependencyinjection.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.dependencyinjection.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "sha512": "ACtnvl3H3M/f8Z42980JxsNu7V9PPbzys4vBs83ZewnsgKd7JeYK18OMPo0g+MxAHrpgMrjmlinXDiaSRPcVnA==", + "type": "package", + "path": "microsoft.extensions.dependencymodel/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyModel.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyModel.targets", + "lib/net462/Microsoft.Extensions.DependencyModel.dll", + "lib/net462/Microsoft.Extensions.DependencyModel.xml", + "lib/net8.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net8.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net9.0/Microsoft.Extensions.DependencyModel.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.xml", + "microsoft.extensions.dependencymodel.9.0.4.nupkg.sha512", + "microsoft.extensions.dependencymodel.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Diagnostics.Abstractions/9.0.4": { + "sha512": "IAucBcHYtiCmMyFag+Vrp5m+cjGRlDttJk9Vx7Dqpq+Ama4BzVUOk0JARQakgFFr7ZTBSgLKlHmtY5MiItB7Cg==", + "type": "package", + "path": "microsoft.extensions.diagnostics.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Diagnostics.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Diagnostics.Abstractions.targets", + "lib/net462/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.Abstractions.xml", + "microsoft.extensions.diagnostics.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.diagnostics.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Diagnostics.HealthChecks/9.0.4": { + "sha512": "jW9lhWQzOOL5sBUCNtAiS6B7tGeLlxJVDjwNuQAQl6dDt9PAAxt3+T2F2jtcvi7KoujgzAdkKQKtGoRaAGlD9w==", + "type": "package", + "path": "microsoft.extensions.diagnostics.healthchecks/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "lib/net462/Microsoft.Extensions.Diagnostics.HealthChecks.dll", + "lib/net462/Microsoft.Extensions.Diagnostics.HealthChecks.xml", + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll", + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.xml", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.HealthChecks.dll", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.HealthChecks.xml", + "microsoft.extensions.diagnostics.healthchecks.9.0.4.nupkg.sha512", + "microsoft.extensions.diagnostics.healthchecks.nuspec" + ] + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/9.0.4": { + "sha512": "XM6WwNbDkVuGhDN89eKxA2Og2eMDXB0PVI7PEzl2R0MbFjYUlfTh7D7vBPEWUVCf2zPDAFiwcMlnVzi6Umq5mg==", + "type": "package", + "path": "microsoft.extensions.diagnostics.healthchecks.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "lib/net462/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.xml", + "microsoft.extensions.diagnostics.healthchecks.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.diagnostics.healthchecks.abstractions.nuspec" + ] + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/9.0.4": { + "sha512": "PdIQYXV2lyBzlQ+zj8+jy+7wxr353MOzOKjqBE2lQWZGFuJZxslmmL8I1gU2+FXE+wGmskSFWZ0n7TZxJu3EgQ==", + "type": "package", + "path": "microsoft.extensions.diagnostics.healthchecks.entityframeworkcore/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "THIRD-PARTY-NOTICES.TXT", + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.dll", + "lib/net9.0/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.xml", + "microsoft.extensions.diagnostics.healthchecks.entityframeworkcore.9.0.4.nupkg.sha512", + "microsoft.extensions.diagnostics.healthchecks.entityframeworkcore.nuspec" + ] + }, + "Microsoft.Extensions.FileProviders.Abstractions/9.0.4": { + "sha512": "gQN2o/KnBfVk6Bd71E2YsvO5lsqrqHmaepDGk+FB/C4aiQY9B0XKKNKfl5/TqcNOs9OEithm4opiMHAErMFyEw==", + "type": "package", + "path": "microsoft.extensions.fileproviders.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.FileProviders.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.FileProviders.Abstractions.targets", + "lib/net462/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/net462/Microsoft.Extensions.FileProviders.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.FileProviders.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.FileProviders.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.xml", + "microsoft.extensions.fileproviders.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.fileproviders.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Hosting.Abstractions/9.0.4": { + "sha512": "bXkwRPMo4x19YKH6/V9XotU7KYQJlihXhcWO1RDclAY3yfY3XNg4QtSEBvng4kK/DnboE0O/nwSl+6Jiv9P+FA==", + "type": "package", + "path": "microsoft.extensions.hosting.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Hosting.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Hosting.Abstractions.targets", + "lib/net462/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Hosting.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Hosting.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Hosting.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.Hosting.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.Hosting.Abstractions.xml", + "microsoft.extensions.hosting.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.hosting.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging/9.0.4": { + "sha512": "xW6QPYsqhbuWBO9/1oA43g/XPKbohJx+7G8FLQgQXIriYvY7s+gxr2wjQJfRoPO900dvvv2vVH7wZovG+M1m6w==", + "type": "package", + "path": "microsoft.extensions.logging/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Logging.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.targets", + "lib/net462/Microsoft.Extensions.Logging.dll", + "lib/net462/Microsoft.Extensions.Logging.xml", + "lib/net8.0/Microsoft.Extensions.Logging.dll", + "lib/net8.0/Microsoft.Extensions.Logging.xml", + "lib/net9.0/Microsoft.Extensions.Logging.dll", + "lib/net9.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.1/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.1/Microsoft.Extensions.Logging.xml", + "microsoft.extensions.logging.9.0.4.nupkg.sha512", + "microsoft.extensions.logging.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "sha512": "0MXlimU4Dud6t+iNi5NEz3dO2w1HXdhoOLaYFuLPCjAsvlPQGwOT6V2KZRMLEhCAm/stSZt1AUv0XmDdkjvtbw==", + "type": "package", + "path": "microsoft.extensions.logging.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net462/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.xml", + "microsoft.extensions.logging.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.logging.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Options/9.0.4": { + "sha512": "fiFI2+58kicqVZyt/6obqoFwHiab7LC4FkQ3mmiBJ28Yy4fAvy2+v9MRnSvvlOO8chTOjKsdafFl/K9veCPo5g==", + "type": "package", + "path": "microsoft.extensions.options/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Options.targets", + "buildTransitive/net462/Microsoft.Extensions.Options.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Options.targets", + "lib/net462/Microsoft.Extensions.Options.dll", + "lib/net462/Microsoft.Extensions.Options.xml", + "lib/net8.0/Microsoft.Extensions.Options.dll", + "lib/net8.0/Microsoft.Extensions.Options.xml", + "lib/net9.0/Microsoft.Extensions.Options.dll", + "lib/net9.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.1/Microsoft.Extensions.Options.dll", + "lib/netstandard2.1/Microsoft.Extensions.Options.xml", + "microsoft.extensions.options.9.0.4.nupkg.sha512", + "microsoft.extensions.options.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "sha512": "SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA==", + "type": "package", + "path": "microsoft.extensions.primitives/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Primitives.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets", + "lib/net462/Microsoft.Extensions.Primitives.dll", + "lib/net462/Microsoft.Extensions.Primitives.xml", + "lib/net8.0/Microsoft.Extensions.Primitives.dll", + "lib/net8.0/Microsoft.Extensions.Primitives.xml", + "lib/net9.0/Microsoft.Extensions.Primitives.dll", + "lib/net9.0/Microsoft.Extensions.Primitives.xml", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.xml", + "microsoft.extensions.primitives.9.0.4.nupkg.sha512", + "microsoft.extensions.primitives.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.IdentityModel.Abstractions/8.0.1": { + "sha512": "OtlIWcyX01olfdevPKZdIPfBEvbcioDyBiE/Z2lHsopsMD7twcKtlN9kMevHmI5IIPhFpfwCIiR6qHQz1WHUIw==", + "type": "package", + "path": "microsoft.identitymodel.abstractions/8.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net462/Microsoft.IdentityModel.Abstractions.dll", + "lib/net462/Microsoft.IdentityModel.Abstractions.xml", + "lib/net472/Microsoft.IdentityModel.Abstractions.dll", + "lib/net472/Microsoft.IdentityModel.Abstractions.xml", + "lib/net6.0/Microsoft.IdentityModel.Abstractions.dll", + "lib/net6.0/Microsoft.IdentityModel.Abstractions.xml", + "lib/net8.0/Microsoft.IdentityModel.Abstractions.dll", + "lib/net8.0/Microsoft.IdentityModel.Abstractions.xml", + "lib/net9.0/Microsoft.IdentityModel.Abstractions.dll", + "lib/net9.0/Microsoft.IdentityModel.Abstractions.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.xml", + "microsoft.identitymodel.abstractions.8.0.1.nupkg.sha512", + "microsoft.identitymodel.abstractions.nuspec" + ] + }, + "Microsoft.IdentityModel.JsonWebTokens/8.0.1": { + "sha512": "s6++gF9x0rQApQzOBbSyp4jUaAlwm+DroKfL8gdOHxs83k8SJfUXhuc46rDB3rNXBQ1MVRxqKUrqFhO/M0E97g==", + "type": "package", + "path": "microsoft.identitymodel.jsonwebtokens/8.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net462/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net462/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/net472/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net472/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/net8.0/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net8.0/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.xml", + "microsoft.identitymodel.jsonwebtokens.8.0.1.nupkg.sha512", + "microsoft.identitymodel.jsonwebtokens.nuspec" + ] + }, + "Microsoft.IdentityModel.Logging/8.0.1": { + "sha512": "UCPF2exZqBXe7v/6sGNiM6zCQOUXXQ9+v5VTb9gPB8ZSUPnX53BxlN78v2jsbIvK9Dq4GovQxo23x8JgWvm/Qg==", + "type": "package", + "path": "microsoft.identitymodel.logging/8.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net462/Microsoft.IdentityModel.Logging.dll", + "lib/net462/Microsoft.IdentityModel.Logging.xml", + "lib/net472/Microsoft.IdentityModel.Logging.dll", + "lib/net472/Microsoft.IdentityModel.Logging.xml", + "lib/net6.0/Microsoft.IdentityModel.Logging.dll", + "lib/net6.0/Microsoft.IdentityModel.Logging.xml", + "lib/net8.0/Microsoft.IdentityModel.Logging.dll", + "lib/net8.0/Microsoft.IdentityModel.Logging.xml", + "lib/net9.0/Microsoft.IdentityModel.Logging.dll", + "lib/net9.0/Microsoft.IdentityModel.Logging.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Logging.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Logging.xml", + "microsoft.identitymodel.logging.8.0.1.nupkg.sha512", + "microsoft.identitymodel.logging.nuspec" + ] + }, + "Microsoft.IdentityModel.Protocols/8.0.1": { + "sha512": "uA2vpKqU3I2mBBEaeJAWPTjT9v1TZrGWKdgK6G5qJd03CLx83kdiqO9cmiK8/n1erkHzFBwU/RphP83aAe3i3g==", + "type": "package", + "path": "microsoft.identitymodel.protocols/8.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net462/Microsoft.IdentityModel.Protocols.dll", + "lib/net462/Microsoft.IdentityModel.Protocols.xml", + "lib/net472/Microsoft.IdentityModel.Protocols.dll", + "lib/net472/Microsoft.IdentityModel.Protocols.xml", + "lib/net6.0/Microsoft.IdentityModel.Protocols.dll", + "lib/net6.0/Microsoft.IdentityModel.Protocols.xml", + "lib/net8.0/Microsoft.IdentityModel.Protocols.dll", + "lib/net8.0/Microsoft.IdentityModel.Protocols.xml", + "lib/net9.0/Microsoft.IdentityModel.Protocols.dll", + "lib/net9.0/Microsoft.IdentityModel.Protocols.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Protocols.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Protocols.xml", + "microsoft.identitymodel.protocols.8.0.1.nupkg.sha512", + "microsoft.identitymodel.protocols.nuspec" + ] + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": { + "sha512": "AQDbfpL+yzuuGhO/mQhKNsp44pm5Jv8/BI4KiFXR7beVGZoSH35zMV3PrmcfvSTsyI6qrcR898NzUauD6SRigg==", + "type": "package", + "path": "microsoft.identitymodel.protocols.openidconnect/8.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net462/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll", + "lib/net462/Microsoft.IdentityModel.Protocols.OpenIdConnect.xml", + "lib/net472/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll", + "lib/net472/Microsoft.IdentityModel.Protocols.OpenIdConnect.xml", + "lib/net6.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll", + "lib/net6.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.xml", + "lib/net8.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll", + "lib/net8.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.xml", + "lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll", + "lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.xml", + "microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512", + "microsoft.identitymodel.protocols.openidconnect.nuspec" + ] + }, + "Microsoft.IdentityModel.Tokens/8.0.1": { + "sha512": "kDimB6Dkd3nkW2oZPDkMkVHfQt3IDqO5gL0oa8WVy3OP4uE8Ij+8TXnqg9TOd9ufjsY3IDiGz7pCUbnfL18tjg==", + "type": "package", + "path": "microsoft.identitymodel.tokens/8.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net462/Microsoft.IdentityModel.Tokens.dll", + "lib/net462/Microsoft.IdentityModel.Tokens.xml", + "lib/net472/Microsoft.IdentityModel.Tokens.dll", + "lib/net472/Microsoft.IdentityModel.Tokens.xml", + "lib/net6.0/Microsoft.IdentityModel.Tokens.dll", + "lib/net6.0/Microsoft.IdentityModel.Tokens.xml", + "lib/net8.0/Microsoft.IdentityModel.Tokens.dll", + "lib/net8.0/Microsoft.IdentityModel.Tokens.xml", + "lib/net9.0/Microsoft.IdentityModel.Tokens.dll", + "lib/net9.0/Microsoft.IdentityModel.Tokens.xml", + "lib/netstandard2.0/Microsoft.IdentityModel.Tokens.dll", + "lib/netstandard2.0/Microsoft.IdentityModel.Tokens.xml", + "microsoft.identitymodel.tokens.8.0.1.nupkg.sha512", + "microsoft.identitymodel.tokens.nuspec" + ] + }, + "Microsoft.OpenApi/1.6.17": { + "sha512": "Le+kehlmrlQfuDFUt1zZ2dVwrhFQtKREdKBo+rexOwaCoYP0/qpgT9tLxCsZjsgR5Itk1UKPcbgO+FyaNid/bA==", + "type": "package", + "path": "microsoft.openapi/1.6.17", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/netstandard2.0/Microsoft.OpenApi.dll", + "lib/netstandard2.0/Microsoft.OpenApi.pdb", + "lib/netstandard2.0/Microsoft.OpenApi.xml", + "microsoft.openapi.1.6.17.nupkg.sha512", + "microsoft.openapi.nuspec" + ] + }, + "Mono.TextTemplating/3.0.0": { + "sha512": "YqueG52R/Xej4VVbKuRIodjiAhV0HR/XVbLbNrJhCZnzjnSjgMJ/dCdV0akQQxavX6hp/LC6rqLGLcXeQYU7XA==", + "type": "package", + "path": "mono.texttemplating/3.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt/LICENSE", + "buildTransitive/Mono.TextTemplating.targets", + "lib/net472/Mono.TextTemplating.dll", + "lib/net6.0/Mono.TextTemplating.dll", + "lib/netstandard2.0/Mono.TextTemplating.dll", + "mono.texttemplating.3.0.0.nupkg.sha512", + "mono.texttemplating.nuspec", + "readme.md" + ] + }, + "Npgsql/9.0.3": { + "sha512": "tPvY61CxOAWxNsKLEBg+oR646X4Bc8UmyQ/tJszL/7mEmIXQnnBhVJZrZEEUv0Bstu0mEsHZD5At3EO8zQRAYw==", + "type": "package", + "path": "npgsql/9.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net6.0/Npgsql.dll", + "lib/net6.0/Npgsql.xml", + "lib/net8.0/Npgsql.dll", + "lib/net8.0/Npgsql.xml", + "npgsql.9.0.3.nupkg.sha512", + "npgsql.nuspec", + "postgresql.png" + ] + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "sha512": "mw5vcY2IEc7L+IeGrxpp/J5OSnCcjkjAgJYCm/eD52wpZze8zsSifdqV7zXslSMmfJG2iIUGZyo3KuDtEFKwMQ==", + "type": "package", + "path": "npgsql.entityframeworkcore.postgresql/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll", + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.xml", + "npgsql.entityframeworkcore.postgresql.9.0.4.nupkg.sha512", + "npgsql.entityframeworkcore.postgresql.nuspec", + "postgresql.png" + ] + }, + "System.CodeDom/6.0.0": { + "sha512": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==", + "type": "package", + "path": "system.codedom/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/netcoreapp2.0/System.CodeDom.targets", + "buildTransitive/netcoreapp3.1/_._", + "lib/net461/System.CodeDom.dll", + "lib/net461/System.CodeDom.xml", + "lib/net6.0/System.CodeDom.dll", + "lib/net6.0/System.CodeDom.xml", + "lib/netstandard2.0/System.CodeDom.dll", + "lib/netstandard2.0/System.CodeDom.xml", + "system.codedom.6.0.0.nupkg.sha512", + "system.codedom.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Collections.Immutable/7.0.0": { + "sha512": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==", + "type": "package", + "path": "system.collections.immutable/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "README.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Collections.Immutable.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Collections.Immutable.targets", + "lib/net462/System.Collections.Immutable.dll", + "lib/net462/System.Collections.Immutable.xml", + "lib/net6.0/System.Collections.Immutable.dll", + "lib/net6.0/System.Collections.Immutable.xml", + "lib/net7.0/System.Collections.Immutable.dll", + "lib/net7.0/System.Collections.Immutable.xml", + "lib/netstandard2.0/System.Collections.Immutable.dll", + "lib/netstandard2.0/System.Collections.Immutable.xml", + "system.collections.immutable.7.0.0.nupkg.sha512", + "system.collections.immutable.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition/7.0.0": { + "sha512": "tRwgcAkDd85O8Aq6zHDANzQaq380cek9lbMg5Qma46u5BZXq/G+XvIYmu+UI+BIIZ9zssXLYrkTykEqxxvhcmg==", + "type": "package", + "path": "system.composition/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.targets", + "lib/net461/_._", + "lib/netcoreapp2.0/_._", + "lib/netstandard2.0/_._", + "system.composition.7.0.0.nupkg.sha512", + "system.composition.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition.AttributedModel/7.0.0": { + "sha512": "2QzClqjElKxgI1jK1Jztnq44/8DmSuTSGGahXqQ4TdEV0h9s2KikQZIgcEqVzR7OuWDFPGLHIprBJGQEPr8fAQ==", + "type": "package", + "path": "system.composition.attributedmodel/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.AttributedModel.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.AttributedModel.targets", + "lib/net462/System.Composition.AttributedModel.dll", + "lib/net462/System.Composition.AttributedModel.xml", + "lib/net6.0/System.Composition.AttributedModel.dll", + "lib/net6.0/System.Composition.AttributedModel.xml", + "lib/net7.0/System.Composition.AttributedModel.dll", + "lib/net7.0/System.Composition.AttributedModel.xml", + "lib/netstandard2.0/System.Composition.AttributedModel.dll", + "lib/netstandard2.0/System.Composition.AttributedModel.xml", + "system.composition.attributedmodel.7.0.0.nupkg.sha512", + "system.composition.attributedmodel.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition.Convention/7.0.0": { + "sha512": "IMhTlpCs4HmlD8B+J8/kWfwX7vrBBOs6xyjSTzBlYSs7W4OET4tlkR/Sg9NG8jkdJH9Mymq0qGdYS1VPqRTBnQ==", + "type": "package", + "path": "system.composition.convention/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.Convention.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.Convention.targets", + "lib/net462/System.Composition.Convention.dll", + "lib/net462/System.Composition.Convention.xml", + "lib/net6.0/System.Composition.Convention.dll", + "lib/net6.0/System.Composition.Convention.xml", + "lib/net7.0/System.Composition.Convention.dll", + "lib/net7.0/System.Composition.Convention.xml", + "lib/netstandard2.0/System.Composition.Convention.dll", + "lib/netstandard2.0/System.Composition.Convention.xml", + "system.composition.convention.7.0.0.nupkg.sha512", + "system.composition.convention.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition.Hosting/7.0.0": { + "sha512": "eB6gwN9S+54jCTBJ5bpwMOVerKeUfGGTYCzz3QgDr1P55Gg/Wb27ShfPIhLMjmZ3MoAKu8uUSv6fcCdYJTN7Bg==", + "type": "package", + "path": "system.composition.hosting/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.Hosting.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.Hosting.targets", + "lib/net462/System.Composition.Hosting.dll", + "lib/net462/System.Composition.Hosting.xml", + "lib/net6.0/System.Composition.Hosting.dll", + "lib/net6.0/System.Composition.Hosting.xml", + "lib/net7.0/System.Composition.Hosting.dll", + "lib/net7.0/System.Composition.Hosting.xml", + "lib/netstandard2.0/System.Composition.Hosting.dll", + "lib/netstandard2.0/System.Composition.Hosting.xml", + "system.composition.hosting.7.0.0.nupkg.sha512", + "system.composition.hosting.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition.Runtime/7.0.0": { + "sha512": "aZJ1Zr5Txe925rbo4742XifEyW0MIni1eiUebmcrP3HwLXZ3IbXUj4MFMUH/RmnJOAQiS401leg/2Sz1MkApDw==", + "type": "package", + "path": "system.composition.runtime/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.Runtime.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.Runtime.targets", + "lib/net462/System.Composition.Runtime.dll", + "lib/net462/System.Composition.Runtime.xml", + "lib/net6.0/System.Composition.Runtime.dll", + "lib/net6.0/System.Composition.Runtime.xml", + "lib/net7.0/System.Composition.Runtime.dll", + "lib/net7.0/System.Composition.Runtime.xml", + "lib/netstandard2.0/System.Composition.Runtime.dll", + "lib/netstandard2.0/System.Composition.Runtime.xml", + "system.composition.runtime.7.0.0.nupkg.sha512", + "system.composition.runtime.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition.TypedParts/7.0.0": { + "sha512": "ZK0KNPfbtxVceTwh+oHNGUOYV2WNOHReX2AXipuvkURC7s/jPwoWfsu3SnDBDgofqbiWr96geofdQ2erm/KTHg==", + "type": "package", + "path": "system.composition.typedparts/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.TypedParts.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.TypedParts.targets", + "lib/net462/System.Composition.TypedParts.dll", + "lib/net462/System.Composition.TypedParts.xml", + "lib/net6.0/System.Composition.TypedParts.dll", + "lib/net6.0/System.Composition.TypedParts.xml", + "lib/net7.0/System.Composition.TypedParts.dll", + "lib/net7.0/System.Composition.TypedParts.xml", + "lib/netstandard2.0/System.Composition.TypedParts.dll", + "lib/netstandard2.0/System.Composition.TypedParts.xml", + "system.composition.typedparts.7.0.0.nupkg.sha512", + "system.composition.typedparts.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.IdentityModel.Tokens.Jwt/8.0.1": { + "sha512": "GJw3bYkWpOgvN3tJo5X4lYUeIFA2HD293FPUhKmp7qxS+g5ywAb34Dnd3cDAFLkcMohy5XTpoaZ4uAHuw0uSPQ==", + "type": "package", + "path": "system.identitymodel.tokens.jwt/8.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net462/System.IdentityModel.Tokens.Jwt.dll", + "lib/net462/System.IdentityModel.Tokens.Jwt.xml", + "lib/net472/System.IdentityModel.Tokens.Jwt.dll", + "lib/net472/System.IdentityModel.Tokens.Jwt.xml", + "lib/net6.0/System.IdentityModel.Tokens.Jwt.dll", + "lib/net6.0/System.IdentityModel.Tokens.Jwt.xml", + "lib/net8.0/System.IdentityModel.Tokens.Jwt.dll", + "lib/net8.0/System.IdentityModel.Tokens.Jwt.xml", + "lib/net9.0/System.IdentityModel.Tokens.Jwt.dll", + "lib/net9.0/System.IdentityModel.Tokens.Jwt.xml", + "lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.dll", + "lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.xml", + "system.identitymodel.tokens.jwt.8.0.1.nupkg.sha512", + "system.identitymodel.tokens.jwt.nuspec" + ] + }, + "System.IO.Pipelines/7.0.0": { + "sha512": "jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg==", + "type": "package", + "path": "system.io.pipelines/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.IO.Pipelines.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.IO.Pipelines.targets", + "lib/net462/System.IO.Pipelines.dll", + "lib/net462/System.IO.Pipelines.xml", + "lib/net6.0/System.IO.Pipelines.dll", + "lib/net6.0/System.IO.Pipelines.xml", + "lib/net7.0/System.IO.Pipelines.dll", + "lib/net7.0/System.IO.Pipelines.xml", + "lib/netstandard2.0/System.IO.Pipelines.dll", + "lib/netstandard2.0/System.IO.Pipelines.xml", + "system.io.pipelines.7.0.0.nupkg.sha512", + "system.io.pipelines.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Reflection.Metadata/7.0.0": { + "sha512": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==", + "type": "package", + "path": "system.reflection.metadata/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "README.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Reflection.Metadata.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Reflection.Metadata.targets", + "lib/net462/System.Reflection.Metadata.dll", + "lib/net462/System.Reflection.Metadata.xml", + "lib/net6.0/System.Reflection.Metadata.dll", + "lib/net6.0/System.Reflection.Metadata.xml", + "lib/net7.0/System.Reflection.Metadata.dll", + "lib/net7.0/System.Reflection.Metadata.xml", + "lib/netstandard2.0/System.Reflection.Metadata.dll", + "lib/netstandard2.0/System.Reflection.Metadata.xml", + "system.reflection.metadata.7.0.0.nupkg.sha512", + "system.reflection.metadata.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "sha512": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + "type": "package", + "path": "system.runtime.compilerservices.unsafe/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/netcoreapp2.0/System.Runtime.CompilerServices.Unsafe.targets", + "buildTransitive/netcoreapp3.1/_._", + "lib/net461/System.Runtime.CompilerServices.Unsafe.dll", + "lib/net461/System.Runtime.CompilerServices.Unsafe.xml", + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll", + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.xml", + "lib/netcoreapp3.1/System.Runtime.CompilerServices.Unsafe.dll", + "lib/netcoreapp3.1/System.Runtime.CompilerServices.Unsafe.xml", + "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll", + "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.xml", + "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512", + "system.runtime.compilerservices.unsafe.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Text.Json/9.0.4": { + "sha512": "pYtmpcO6R3Ef1XilZEHgXP2xBPVORbYEzRP7dl0IAAbN8Dm+kfwio8aCKle97rAWXOExr292MuxWYurIuwN62g==", + "type": "package", + "path": "system.text.json/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "buildTransitive/net461/System.Text.Json.targets", + "buildTransitive/net462/System.Text.Json.targets", + "buildTransitive/net8.0/System.Text.Json.targets", + "buildTransitive/netcoreapp2.0/System.Text.Json.targets", + "buildTransitive/netstandard2.0/System.Text.Json.targets", + "lib/net462/System.Text.Json.dll", + "lib/net462/System.Text.Json.xml", + "lib/net8.0/System.Text.Json.dll", + "lib/net8.0/System.Text.Json.xml", + "lib/net9.0/System.Text.Json.dll", + "lib/net9.0/System.Text.Json.xml", + "lib/netstandard2.0/System.Text.Json.dll", + "lib/netstandard2.0/System.Text.Json.xml", + "system.text.json.9.0.4.nupkg.sha512", + "system.text.json.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Threading.Channels/7.0.0": { + "sha512": "qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA==", + "type": "package", + "path": "system.threading.channels/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Threading.Channels.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Threading.Channels.targets", + "lib/net462/System.Threading.Channels.dll", + "lib/net462/System.Threading.Channels.xml", + "lib/net6.0/System.Threading.Channels.dll", + "lib/net6.0/System.Threading.Channels.xml", + "lib/net7.0/System.Threading.Channels.dll", + "lib/net7.0/System.Threading.Channels.xml", + "lib/netstandard2.0/System.Threading.Channels.dll", + "lib/netstandard2.0/System.Threading.Channels.xml", + "lib/netstandard2.1/System.Threading.Channels.dll", + "lib/netstandard2.1/System.Threading.Channels.xml", + "system.threading.channels.7.0.0.nupkg.sha512", + "system.threading.channels.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "PleasePayMe.Application/1.0.0": { + "type": "project", + "path": "../PleasePayMe.Application/PleasePayMe.Application.csproj", + "msbuildProject": "../PleasePayMe.Application/PleasePayMe.Application.csproj" + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "path": "../PleasePayMe.Domain/PleasePayMe.Domain.csproj", + "msbuildProject": "../PleasePayMe.Domain/PleasePayMe.Domain.csproj" + }, + "PleasePayMe.Infrastructure/1.0.0": { + "type": "project", + "path": "../PleasePayMe.Infrastructure/PleasePayMe.Infrastructure.csproj", + "msbuildProject": "../PleasePayMe.Infrastructure/PleasePayMe.Infrastructure.csproj" + } + }, + "projectFileDependencyGroups": { + "net9.0": [ + "Microsoft.AspNetCore.Authentication.JwtBearer >= 9.0.4", + "Microsoft.AspNetCore.OpenApi >= 9.0.17", + "Microsoft.EntityFrameworkCore.Design >= 9.0.4", + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore >= 9.0.4", + "PleasePayMe.Application >= 1.0.0", + "PleasePayMe.Infrastructure >= 1.0.0" + ] + }, + "packageFolders": { + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Api\\PleasePayMe.Api.csproj", + "projectName": "PleasePayMe.Api", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Api\\PleasePayMe.Api.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Api\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj" + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "dependencies": { + "Microsoft.AspNetCore.Authentication.JwtBearer": { + "target": "Package", + "version": "[9.0.4, )" + }, + "Microsoft.AspNetCore.OpenApi": { + "target": "Package", + "version": "[9.0.17, )" + }, + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[9.0.4, )" + }, + "Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore": { + "target": "Package", + "version": "[9.0.4, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Api/obj/project.nuget.cache b/src/PleasePayMe.Api/obj/project.nuget.cache new file mode 100644 index 0000000..6515658 --- /dev/null +++ b/src/PleasePayMe.Api/obj/project.nuget.cache @@ -0,0 +1,66 @@ +{ + "version": 2, + "dgSpecHash": "pzLYlBQztVs=", + "success": true, + "projectFilePath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Api\\PleasePayMe.Api.csproj", + "expectedPackageFiles": [ + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\humanizer.core\\2.14.1\\humanizer.core.2.14.1.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.aspnetcore.authentication.jwtbearer\\9.0.4\\microsoft.aspnetcore.authentication.jwtbearer.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.aspnetcore.openapi\\9.0.17\\microsoft.aspnetcore.openapi.9.0.17.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.bcl.asyncinterfaces\\7.0.0\\microsoft.bcl.asyncinterfaces.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.build.framework\\17.8.3\\microsoft.build.framework.17.8.3.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.build.locator\\1.7.8\\microsoft.build.locator.1.7.8.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.analyzers\\3.3.4\\microsoft.codeanalysis.analyzers.3.3.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.common\\4.8.0\\microsoft.codeanalysis.common.4.8.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.csharp\\4.8.0\\microsoft.codeanalysis.csharp.4.8.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.csharp.workspaces\\4.8.0\\microsoft.codeanalysis.csharp.workspaces.4.8.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.workspaces.common\\4.8.0\\microsoft.codeanalysis.workspaces.common.4.8.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.workspaces.msbuild\\4.8.0\\microsoft.codeanalysis.workspaces.msbuild.4.8.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.entityframeworkcore\\9.0.4\\microsoft.entityframeworkcore.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.entityframeworkcore.abstractions\\9.0.4\\microsoft.entityframeworkcore.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.entityframeworkcore.analyzers\\9.0.4\\microsoft.entityframeworkcore.analyzers.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.entityframeworkcore.design\\9.0.4\\microsoft.entityframeworkcore.design.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.entityframeworkcore.relational\\9.0.4\\microsoft.entityframeworkcore.relational.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.caching.abstractions\\9.0.4\\microsoft.extensions.caching.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.caching.memory\\9.0.4\\microsoft.extensions.caching.memory.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.configuration.abstractions\\9.0.4\\microsoft.extensions.configuration.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.dependencyinjection\\9.0.4\\microsoft.extensions.dependencyinjection.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.dependencyinjection.abstractions\\9.0.4\\microsoft.extensions.dependencyinjection.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.dependencymodel\\9.0.4\\microsoft.extensions.dependencymodel.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.diagnostics.abstractions\\9.0.4\\microsoft.extensions.diagnostics.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.diagnostics.healthchecks\\9.0.4\\microsoft.extensions.diagnostics.healthchecks.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.diagnostics.healthchecks.abstractions\\9.0.4\\microsoft.extensions.diagnostics.healthchecks.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.diagnostics.healthchecks.entityframeworkcore\\9.0.4\\microsoft.extensions.diagnostics.healthchecks.entityframeworkcore.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.fileproviders.abstractions\\9.0.4\\microsoft.extensions.fileproviders.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.hosting.abstractions\\9.0.4\\microsoft.extensions.hosting.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.logging\\9.0.4\\microsoft.extensions.logging.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.logging.abstractions\\9.0.4\\microsoft.extensions.logging.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.options\\9.0.4\\microsoft.extensions.options.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.primitives\\9.0.4\\microsoft.extensions.primitives.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.identitymodel.abstractions\\8.0.1\\microsoft.identitymodel.abstractions.8.0.1.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.identitymodel.jsonwebtokens\\8.0.1\\microsoft.identitymodel.jsonwebtokens.8.0.1.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.identitymodel.logging\\8.0.1\\microsoft.identitymodel.logging.8.0.1.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.identitymodel.protocols\\8.0.1\\microsoft.identitymodel.protocols.8.0.1.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.identitymodel.protocols.openidconnect\\8.0.1\\microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.identitymodel.tokens\\8.0.1\\microsoft.identitymodel.tokens.8.0.1.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.openapi\\1.6.17\\microsoft.openapi.1.6.17.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\mono.texttemplating\\3.0.0\\mono.texttemplating.3.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\npgsql\\9.0.3\\npgsql.9.0.3.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\npgsql.entityframeworkcore.postgresql\\9.0.4\\npgsql.entityframeworkcore.postgresql.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.codedom\\6.0.0\\system.codedom.6.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.collections.immutable\\7.0.0\\system.collections.immutable.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition\\7.0.0\\system.composition.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition.attributedmodel\\7.0.0\\system.composition.attributedmodel.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition.convention\\7.0.0\\system.composition.convention.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition.hosting\\7.0.0\\system.composition.hosting.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition.runtime\\7.0.0\\system.composition.runtime.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition.typedparts\\7.0.0\\system.composition.typedparts.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.identitymodel.tokens.jwt\\8.0.1\\system.identitymodel.tokens.jwt.8.0.1.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.io.pipelines\\7.0.0\\system.io.pipelines.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.reflection.metadata\\7.0.0\\system.reflection.metadata.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.runtime.compilerservices.unsafe\\6.0.0\\system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.text.json\\9.0.4\\system.text.json.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.threading.channels\\7.0.0\\system.threading.channels.7.0.0.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/src/PleasePayMe.Application/Abstractions/IBudgetService.cs b/src/PleasePayMe.Application/Abstractions/IBudgetService.cs new file mode 100644 index 0000000..999a422 --- /dev/null +++ b/src/PleasePayMe.Application/Abstractions/IBudgetService.cs @@ -0,0 +1,76 @@ +using PleasePayMe.Application.Contracts; + +namespace PleasePayMe.Application.Abstractions; + +public interface IBudgetService +{ + Task> ListUserStatusesAsync(long userId, CancellationToken ct); + Task> ListAllStatusesAsync(CancellationToken ct); + Task GetStatusAsync(long userId, long? budgetId, CancellationToken ct); + Task CreateBudgetAsync( + long userId, + decimal totalAmount, + DateOnly endDate, + string name, + bool isActive, + bool select, + DateOnly? startDate, + CancellationToken ct); + Task UpdateBudgetAsync( + long userId, + long budgetId, + string? name, + decimal? totalAmount, + DateOnly? endDate, + DateOnly? startDate, + bool resetExpenses, + CancellationToken ct); + Task SetBudgetActiveAsync(long userId, long budgetId, bool isActive, CancellationToken ct); + Task DeleteBudgetAsync(long userId, long budgetId, CancellationToken ct); + Task SelectBudgetAsync(long userId, long budgetId, CancellationToken ct); + Task UpsertBudgetAsync( + long userId, + decimal totalAmount, + DateOnly endDate, + bool resetExpenses, + string? name, + long? budgetId, + DateOnly? startDate, + CancellationToken ct); + Task AddExpenseAsync( + long userId, + decimal amount, + string? note, + DateOnly? spentAt, + long? budgetId, + CancellationToken ct); + Task<(BudgetStatusDto Status, decimal DeletedAmount)?> UndoLastExpenseAsync( + long userId, + long? budgetId, + CancellationToken ct); + Task GetPeriodExpensesPageAsync( + long userId, + int page, + int pageSize, + long? budgetId, + CancellationToken ct); + Task GetExpensesOnDatePageAsync( + long userId, + DateOnly day, + int page, + int pageSize, + long? budgetId, + CancellationToken ct); + Task GetAllExpensesPageAsync( + long userId, + int page, + int pageSize, + DateOnly? spentAt, + CancellationToken ct); + Task GetExpensesInRangeAsync( + long userId, + DateOnly from, + DateOnly to, + long? budgetId, + CancellationToken ct); +} diff --git a/src/PleasePayMe.Application/Abstractions/IJobService.cs b/src/PleasePayMe.Application/Abstractions/IJobService.cs new file mode 100644 index 0000000..7f5f941 --- /dev/null +++ b/src/PleasePayMe.Application/Abstractions/IJobService.cs @@ -0,0 +1,30 @@ +using PleasePayMe.Application.Contracts; +using PleasePayMe.Domain; + +namespace PleasePayMe.Application.Abstractions; + +public interface IJobService +{ + Task> ListAsync(long userId, CancellationToken ct); + Task GetAsync(long userId, long jobId, CancellationToken ct); + Task CreateAsync( + long userId, + string name, + decimal salaryAmount, + IReadOnlyList payDays, + decimal firstPayPercent, + WeekendPayPolicy weekendPolicy, + bool isActive, + CancellationToken ct); + Task UpdateAsync( + long userId, + long jobId, + string name, + decimal salaryAmount, + IReadOnlyList payDays, + decimal firstPayPercent, + WeekendPayPolicy weekendPolicy, + bool isActive, + CancellationToken ct); + Task DeleteAsync(long userId, long jobId, CancellationToken ct); +} diff --git a/src/PleasePayMe.Application/Abstractions/ITelegramLinkService.cs b/src/PleasePayMe.Application/Abstractions/ITelegramLinkService.cs new file mode 100644 index 0000000..d925ec4 --- /dev/null +++ b/src/PleasePayMe.Application/Abstractions/ITelegramLinkService.cs @@ -0,0 +1,13 @@ +namespace PleasePayMe.Application.Abstractions; + +public interface ITelegramLinkService +{ + Task FindYandexUserIdAsync(long telegramUserId, CancellationToken ct); + + /// + /// Returns a reusable unexpired challenge token for this Telegram user. + /// + Task CreateOrReuseChallengeTokenAsync(long telegramUserId, CancellationToken ct); + + Task CompleteAsync(string token, long yandexUserId, CancellationToken ct); +} diff --git a/src/PleasePayMe.Application/Contracts/BudgetDtos.cs b/src/PleasePayMe.Application/Contracts/BudgetDtos.cs new file mode 100644 index 0000000..ff02288 --- /dev/null +++ b/src/PleasePayMe.Application/Contracts/BudgetDtos.cs @@ -0,0 +1,28 @@ +using PleasePayMe.Domain.Entities; + +namespace PleasePayMe.Application.Contracts; + +public sealed record BudgetStatusDto( + Budget Budget, + DateOnly Today, + int DaysLeft, + decimal TotalSpent, + decimal Remaining, + decimal DailyLimit, + decimal SpentToday, + decimal RemainingToday, + bool IsOverDaily, + bool IsOverBudget, + bool IsExpired, + bool Selected); + +public sealed record ExpensesPageDto( + Budget? Budget, + int Page, + int TotalPages, + int TotalCount, + decimal TotalSum, + int PageSize, + IReadOnlyList Items); + +public sealed record ExpensesRangeDto(IReadOnlyList Items); diff --git a/src/PleasePayMe.Application/Contracts/JobDtos.cs b/src/PleasePayMe.Application/Contracts/JobDtos.cs new file mode 100644 index 0000000..2bb713f --- /dev/null +++ b/src/PleasePayMe.Application/Contracts/JobDtos.cs @@ -0,0 +1,21 @@ +using PleasePayMe.Domain; + +namespace PleasePayMe.Application.Contracts; + +public sealed record UpcomingPayDto( + DateOnly Date, + int ScheduledDay, + decimal Percent, + decimal Amount); + +public sealed record JobDto( + long Id, + long UserId, + string Name, + decimal SalaryAmount, + string Currency, + IReadOnlyList PayDays, + decimal FirstPayPercent, + WeekendPayPolicy WeekendPolicy, + bool IsActive, + IReadOnlyList NextPays); diff --git a/src/PleasePayMe.Application/Jobs/PaySchedule.cs b/src/PleasePayMe.Application/Jobs/PaySchedule.cs new file mode 100644 index 0000000..15a74f6 --- /dev/null +++ b/src/PleasePayMe.Application/Jobs/PaySchedule.cs @@ -0,0 +1,117 @@ +using PleasePayMe.Domain; + +namespace PleasePayMe.Application.Jobs; + +public static class PaySchedule +{ + public const int MaxPayDays = 2; + + public static IReadOnlyList NormalizePayDays(IEnumerable days) + { + var normalized = days + .Where(d => d is >= 1 and <= 31) + .Distinct() + .OrderBy(d => d) + .ToList(); + if (normalized.Count == 0) + { + throw new DomainException("Укажи хотя бы один день выплаты (1–31), максимум два"); + } + + if (normalized.Count > MaxPayDays) + { + throw new DomainException("Можно выбрать максимум 2 дня выплаты"); + } + + return normalized; + } + + public static decimal NormalizeFirstPayPercent(int payDayCount, decimal firstPayPercent) + { + if (payDayCount <= 1) + { + return 100m; + } + + if (firstPayPercent is < 0 or > 100) + { + throw new DomainException("Процент первой выплаты должен быть от 0 до 100"); + } + + return Math.Round(firstPayPercent, 2); + } + + public static DateOnly AdjustForWeekend(DateOnly date, WeekendPayPolicy policy) + { + return date.DayOfWeek switch + { + DayOfWeek.Saturday => policy == WeekendPayPolicy.BeforeWeekend + ? date.AddDays(-1) + : date.AddDays(2), + DayOfWeek.Sunday => policy == WeekendPayPolicy.BeforeWeekend + ? date.AddDays(-2) + : date.AddDays(1), + _ => date, + }; + } + + public static DateOnly NominalDate(int year, int month, int dayOfMonth) + { + var daysInMonth = DateTime.DaysInMonth(year, month); + var actualDay = Math.Min(dayOfMonth, daysInMonth); + return new DateOnly(year, month, actualDay); + } + + public static IReadOnlyList<(DateOnly Date, int ScheduledDay, decimal Percent, decimal Amount)> NextPays( + IReadOnlyList payDays, + decimal salaryAmount, + decimal firstPayPercent, + WeekendPayPolicy weekendPolicy, + DateOnly from, + int count = 4) + { + if (payDays.Count == 0 || count <= 0) + { + return Array.Empty<(DateOnly, int, decimal, decimal)>(); + } + + var ordered = payDays.OrderBy(d => d).ToArray(); + var firstPercent = NormalizeFirstPayPercent(ordered.Length, firstPayPercent); + var secondPercent = ordered.Length == 1 ? 0m : 100m - firstPercent; + + var result = new List<(DateOnly Date, int ScheduledDay, decimal Percent, decimal Amount)>(count); + var cursor = new DateOnly(from.Year, from.Month, 1); + + for (var guard = 0; result.Count < count && guard < 48; guard++) + { + var year = cursor.Year; + var month = cursor.Month; + + for (var i = 0; i < ordered.Length; i++) + { + var scheduledDay = ordered[i]; + var nominal = NominalDate(year, month, scheduledDay); + var actual = AdjustForWeekend(nominal, weekendPolicy); + var percent = i == 0 ? firstPercent : secondPercent; + var amount = Math.Round(salaryAmount * percent / 100m, 2); + + if (actual >= from) + { + result.Add((actual, scheduledDay, percent, amount)); + if (result.Count >= count) + { + break; + } + } + } + + cursor = cursor.AddMonths(1); + } + + return result + .OrderBy(x => x.Date) + .ThenBy(x => x.ScheduledDay) + .Take(count) + .ToList(); + } +} diff --git a/src/PleasePayMe.Application/PleasePayMe.Application.csproj b/src/PleasePayMe.Application/PleasePayMe.Application.csproj new file mode 100644 index 0000000..6f436bd --- /dev/null +++ b/src/PleasePayMe.Application/PleasePayMe.Application.csproj @@ -0,0 +1,13 @@ + + + + + + + + net9.0 + enable + enable + + + diff --git a/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Application.deps.json b/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Application.deps.json new file mode 100644 index 0000000..b5d6503 --- /dev/null +++ b/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Application.deps.json @@ -0,0 +1,39 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v9.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v9.0": { + "PleasePayMe.Application/1.0.0": { + "dependencies": { + "PleasePayMe.Domain": "1.0.0" + }, + "runtime": { + "PleasePayMe.Application.dll": {} + } + }, + "PleasePayMe.Domain/1.0.0": { + "runtime": { + "PleasePayMe.Domain.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "PleasePayMe.Application/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Application.dll b/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Application.dll new file mode 100644 index 0000000..bfec08b Binary files /dev/null and b/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Application.pdb b/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Application.pdb new file mode 100644 index 0000000..c531d97 Binary files /dev/null and b/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Application.pdb differ diff --git a/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..e1642b6 Binary files /dev/null and b/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..9c94240 Binary files /dev/null and b/src/PleasePayMe.Application/bin/Debug/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Application.deps.json b/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Application.deps.json new file mode 100644 index 0000000..b5d6503 --- /dev/null +++ b/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Application.deps.json @@ -0,0 +1,39 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v9.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v9.0": { + "PleasePayMe.Application/1.0.0": { + "dependencies": { + "PleasePayMe.Domain": "1.0.0" + }, + "runtime": { + "PleasePayMe.Application.dll": {} + } + }, + "PleasePayMe.Domain/1.0.0": { + "runtime": { + "PleasePayMe.Domain.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "PleasePayMe.Application/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Application.dll b/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Application.dll new file mode 100644 index 0000000..8eec7c1 Binary files /dev/null and b/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Application.pdb b/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Application.pdb new file mode 100644 index 0000000..5bc04bf Binary files /dev/null and b/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Application.pdb differ diff --git a/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..0c885d2 Binary files /dev/null and b/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..db59d49 Binary files /dev/null and b/src/PleasePayMe.Application/bin/Release/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/src/PleasePayMe.Application/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/src/PleasePayMe.Application/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePa.0E29C28E.Up2Date b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePa.0E29C28E.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.AssemblyInfo.cs b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.AssemblyInfo.cs new file mode 100644 index 0000000..0ab3a03 --- /dev/null +++ b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("PleasePayMe.Application")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("PleasePayMe.Application")] +[assembly: System.Reflection.AssemblyTitleAttribute("PleasePayMe.Application")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.AssemblyInfoInputs.cache b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.AssemblyInfoInputs.cache new file mode 100644 index 0000000..6644175 --- /dev/null +++ b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +80e040c8acc0de60ba33c8e01c39ab8f99c0c1d233cdd6586fd257bfd0ce96f7 diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.GeneratedMSBuildEditorConfig.editorconfig b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..7988bcd --- /dev/null +++ b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = PleasePayMe.Application +build_property.ProjectDir = c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.GlobalUsings.g.cs b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.assets.cache b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.assets.cache new file mode 100644 index 0000000..a5296ef Binary files /dev/null and b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.assets.cache differ diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.csproj.AssemblyReference.cache b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.csproj.AssemblyReference.cache new file mode 100644 index 0000000..406324c Binary files /dev/null and b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.csproj.AssemblyReference.cache differ diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.csproj.CoreCompileInputs.cache b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..61f681f --- /dev/null +++ b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +320248325a851f37f9d68bbc40dc0b63c8b6591861a2908c22288111215225e2 diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.csproj.FileListAbsolute.txt b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..6701f40 --- /dev/null +++ b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.csproj.FileListAbsolute.txt @@ -0,0 +1,15 @@ +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\bin\Debug\net9.0\PleasePayMe.Application.deps.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\bin\Debug\net9.0\PleasePayMe.Application.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\bin\Debug\net9.0\PleasePayMe.Application.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\bin\Debug\net9.0\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\bin\Debug\net9.0\PleasePayMe.Domain.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Debug\net9.0\PleasePayMe.Application.csproj.AssemblyReference.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Debug\net9.0\PleasePayMe.Application.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Debug\net9.0\PleasePayMe.Application.AssemblyInfoInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Debug\net9.0\PleasePayMe.Application.AssemblyInfo.cs +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Debug\net9.0\PleasePayMe.Application.csproj.CoreCompileInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Debug\net9.0\PleasePa.0E29C28E.Up2Date +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Debug\net9.0\PleasePayMe.Application.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Debug\net9.0\refint\PleasePayMe.Application.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Debug\net9.0\PleasePayMe.Application.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Debug\net9.0\ref\PleasePayMe.Application.dll diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.dll b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.dll new file mode 100644 index 0000000..bfec08b Binary files /dev/null and b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.pdb b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.pdb new file mode 100644 index 0000000..c531d97 Binary files /dev/null and b/src/PleasePayMe.Application/obj/Debug/net9.0/PleasePayMe.Application.pdb differ diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/ref/PleasePayMe.Application.dll b/src/PleasePayMe.Application/obj/Debug/net9.0/ref/PleasePayMe.Application.dll new file mode 100644 index 0000000..457ee3f Binary files /dev/null and b/src/PleasePayMe.Application/obj/Debug/net9.0/ref/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Application/obj/Debug/net9.0/refint/PleasePayMe.Application.dll b/src/PleasePayMe.Application/obj/Debug/net9.0/refint/PleasePayMe.Application.dll new file mode 100644 index 0000000..457ee3f Binary files /dev/null and b/src/PleasePayMe.Application/obj/Debug/net9.0/refint/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Application/obj/PleasePayMe.Application.csproj.nuget.dgspec.json b/src/PleasePayMe.Application/obj/PleasePayMe.Application.csproj.nuget.dgspec.json new file mode 100644 index 0000000..149948b --- /dev/null +++ b/src/PleasePayMe.Application/obj/PleasePayMe.Application.csproj.nuget.dgspec.json @@ -0,0 +1,130 @@ +{ + "format": 1, + "restore": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj": {} + }, + "projects": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj", + "projectName": "PleasePayMe.Application", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "projectName": "PleasePayMe.Domain", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Application/obj/PleasePayMe.Application.csproj.nuget.g.props b/src/PleasePayMe.Application/obj/PleasePayMe.Application.csproj.nuget.g.props new file mode 100644 index 0000000..d608f0f --- /dev/null +++ b/src/PleasePayMe.Application/obj/PleasePayMe.Application.csproj.nuget.g.props @@ -0,0 +1,15 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget + C:\Users\ggpo1\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget + PackageReference + 6.14.3 + + + + + \ No newline at end of file diff --git a/src/PleasePayMe.Application/obj/PleasePayMe.Application.csproj.nuget.g.targets b/src/PleasePayMe.Application/obj/PleasePayMe.Application.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/src/PleasePayMe.Application/obj/PleasePayMe.Application.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/src/PleasePayMe.Application/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/src/PleasePayMe.Application/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePa.0E29C28E.Up2Date b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePa.0E29C28E.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.AssemblyInfo.cs b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.AssemblyInfo.cs new file mode 100644 index 0000000..7e68b5a --- /dev/null +++ b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("PleasePayMe.Application")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("PleasePayMe.Application")] +[assembly: System.Reflection.AssemblyTitleAttribute("PleasePayMe.Application")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.AssemblyInfoInputs.cache b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.AssemblyInfoInputs.cache new file mode 100644 index 0000000..9941050 --- /dev/null +++ b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +ac2c39b6b61b9c5016d596feac325ccc18aac6ed60c9dbee0c4d12683317b049 diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.GeneratedMSBuildEditorConfig.editorconfig b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..c412ad5 --- /dev/null +++ b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = PleasePayMe.Application +build_property.ProjectDir = C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.GlobalUsings.g.cs b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.assets.cache b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.assets.cache new file mode 100644 index 0000000..071c73f Binary files /dev/null and b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.assets.cache differ diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.csproj.AssemblyReference.cache b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.csproj.AssemblyReference.cache new file mode 100644 index 0000000..fcedab0 Binary files /dev/null and b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.csproj.AssemblyReference.cache differ diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.csproj.CoreCompileInputs.cache b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..f33d872 --- /dev/null +++ b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +67f8b12873a7f88b645f11f2ebadc199a4094da90e615abb0b6ee9056b9eb10a diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.csproj.FileListAbsolute.txt b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..0fc6491 --- /dev/null +++ b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.csproj.FileListAbsolute.txt @@ -0,0 +1,15 @@ +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\bin\Release\net9.0\PleasePayMe.Application.deps.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\bin\Release\net9.0\PleasePayMe.Application.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\bin\Release\net9.0\PleasePayMe.Application.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\bin\Release\net9.0\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\bin\Release\net9.0\PleasePayMe.Domain.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Release\net9.0\PleasePayMe.Application.csproj.AssemblyReference.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Release\net9.0\PleasePayMe.Application.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Release\net9.0\PleasePayMe.Application.AssemblyInfoInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Release\net9.0\PleasePayMe.Application.AssemblyInfo.cs +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Release\net9.0\PleasePayMe.Application.csproj.CoreCompileInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Release\net9.0\PleasePa.0E29C28E.Up2Date +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Release\net9.0\PleasePayMe.Application.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Release\net9.0\refint\PleasePayMe.Application.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Release\net9.0\PleasePayMe.Application.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Application\obj\Release\net9.0\ref\PleasePayMe.Application.dll diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.dll b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.dll new file mode 100644 index 0000000..8eec7c1 Binary files /dev/null and b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.pdb b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.pdb new file mode 100644 index 0000000..5bc04bf Binary files /dev/null and b/src/PleasePayMe.Application/obj/Release/net9.0/PleasePayMe.Application.pdb differ diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/ref/PleasePayMe.Application.dll b/src/PleasePayMe.Application/obj/Release/net9.0/ref/PleasePayMe.Application.dll new file mode 100644 index 0000000..c763df2 Binary files /dev/null and b/src/PleasePayMe.Application/obj/Release/net9.0/ref/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Application/obj/Release/net9.0/refint/PleasePayMe.Application.dll b/src/PleasePayMe.Application/obj/Release/net9.0/refint/PleasePayMe.Application.dll new file mode 100644 index 0000000..c763df2 Binary files /dev/null and b/src/PleasePayMe.Application/obj/Release/net9.0/refint/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Application/obj/project.assets.json b/src/PleasePayMe.Application/obj/project.assets.json new file mode 100644 index 0000000..94b4972 --- /dev/null +++ b/src/PleasePayMe.Application/obj/project.assets.json @@ -0,0 +1,95 @@ +{ + "version": 3, + "targets": { + "net9.0": { + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v9.0", + "compile": { + "bin/placeholder/PleasePayMe.Domain.dll": {} + }, + "runtime": { + "bin/placeholder/PleasePayMe.Domain.dll": {} + } + } + } + }, + "libraries": { + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "path": "../PleasePayMe.Domain/PleasePayMe.Domain.csproj", + "msbuildProject": "../PleasePayMe.Domain/PleasePayMe.Domain.csproj" + } + }, + "projectFileDependencyGroups": { + "net9.0": [ + "PleasePayMe.Domain >= 1.0.0" + ] + }, + "packageFolders": { + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj", + "projectName": "PleasePayMe.Application", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Application/obj/project.nuget.cache b/src/PleasePayMe.Application/obj/project.nuget.cache new file mode 100644 index 0000000..ad09c91 --- /dev/null +++ b/src/PleasePayMe.Application/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "iw9nQlVdqKA=", + "success": true, + "projectFilePath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file diff --git a/src/PleasePayMe.Domain.Tests/PleasePayMe.Domain.Tests.csproj b/src/PleasePayMe.Domain.Tests/PleasePayMe.Domain.Tests.csproj new file mode 100644 index 0000000..647a455 --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/PleasePayMe.Domain.Tests.csproj @@ -0,0 +1,23 @@ + + + + net9.0 + enable + enable + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/src/PleasePayMe.Domain.Tests/YandexIdentityTests.cs b/src/PleasePayMe.Domain.Tests/YandexIdentityTests.cs new file mode 100644 index 0000000..77e941b --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/YandexIdentityTests.cs @@ -0,0 +1,55 @@ +using PleasePayMe.Domain; +using Xunit; + +namespace PleasePayMe.Domain.Tests; + +public sealed class YandexIdentityTests +{ + [Fact] + public void Namespaces_yandex_ids_below_javascript_safe_integer() + { + var internalId = YandexIdentity.ToInternalUserId(42); + + Assert.Equal(YandexIdentity.NamespaceBit | 42, internalId); + Assert.True(internalId < (1L << 53)); + Assert.NotEqual(42, internalId); + Assert.True(YandexIdentity.IsYandexUserId(internalId)); + Assert.False(YandexIdentity.IsYandexUserId(42)); + Assert.False(YandexIdentity.IsYandexUserId(0)); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + [InlineData(YandexIdentity.NamespaceBit)] + [InlineData(YandexIdentity.NamespaceBit + 1)] + public void Rejects_non_positive_or_overflowing_ids(long yandexId) + { + Assert.Throws(() => YandexIdentity.ToInternalUserId(yandexId)); + } +} + +public sealed class OAuthRedirectAllowlistTests +{ + [Fact] + public void Accepts_registered_login_callback() + { + var allowlist = new[] { "https://please-pay-me.ru/" }; + + Assert.True(OAuthRedirectAllowlist.Contains(allowlist, "https://please-pay-me.ru/")); + Assert.True(OAuthRedirectAllowlist.Contains(allowlist, "https://please-pay-me.ru")); + Assert.False(OAuthRedirectAllowlist.Contains(allowlist, "https://evil.example/")); + Assert.False(OAuthRedirectAllowlist.Contains(allowlist, "javascript:alert(1)")); + } + + [Fact] + public void Parse_splits_and_drops_junk() + { + var parsed = OAuthRedirectAllowlist.Parse( + "https://please-pay-me.ru/login, http://localhost:5173/login", + "not-a-uri"); + + Assert.Equal(2, parsed.Count); + Assert.Contains("https://please-pay-me.ru/login", parsed); + } +} diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/.msCoverageSourceRootsMapping_PleasePayMe.Domain.Tests b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/.msCoverageSourceRootsMapping_PleasePayMe.Domain.Tests new file mode 100644 index 0000000..c1e47c8 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/.msCoverageSourceRootsMapping_PleasePayMe.Domain.Tests differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.CommunicationUtilities.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.CommunicationUtilities.dll new file mode 100644 index 0000000..f018486 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.CommunicationUtilities.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.CoreUtilities.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.CoreUtilities.dll new file mode 100644 index 0000000..da3396e Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.CoreUtilities.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.CrossPlatEngine.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.CrossPlatEngine.dll new file mode 100644 index 0000000..d3f461d Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.CrossPlatEngine.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.PlatformAbstractions.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.PlatformAbstractions.dll new file mode 100644 index 0000000..ad4e8c7 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.PlatformAbstractions.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.Utilities.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.Utilities.dll new file mode 100644 index 0000000..02ccb37 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.TestPlatform.Utilities.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.VisualStudio.CodeCoverage.Shim.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.VisualStudio.CodeCoverage.Shim.dll new file mode 100644 index 0000000..43d029f Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.VisualStudio.CodeCoverage.Shim.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.VisualStudio.TestPlatform.Common.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.VisualStudio.TestPlatform.Common.dll new file mode 100644 index 0000000..0fa6eb1 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.VisualStudio.TestPlatform.Common.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll new file mode 100644 index 0000000..bb7a277 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Newtonsoft.Json.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Newtonsoft.Json.dll new file mode 100644 index 0000000..1ffeabe Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/Newtonsoft.Json.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.deps.json b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.deps.json new file mode 100644 index 0000000..5837add --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.deps.json @@ -0,0 +1,462 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v9.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v9.0": { + "PleasePayMe.Domain.Tests/1.0.0": { + "dependencies": { + "Microsoft.NET.Test.Sdk": "17.12.0", + "PleasePayMe.Domain": "1.0.0", + "xunit": "2.9.2", + "xunit.runner.visualstudio": "2.8.2" + }, + "runtime": { + "PleasePayMe.Domain.Tests.dll": {} + } + }, + "Microsoft.CodeCoverage/17.12.0": { + "runtime": { + "lib/netcoreapp3.1/Microsoft.VisualStudio.CodeCoverage.Shim.dll": { + "assemblyVersion": "15.0.0.0", + "fileVersion": "17.1200.524.48002" + } + } + }, + "Microsoft.NET.Test.Sdk/17.12.0": { + "dependencies": { + "Microsoft.CodeCoverage": "17.12.0", + "Microsoft.TestPlatform.TestHost": "17.12.0" + } + }, + "Microsoft.TestPlatform.ObjectModel/17.12.0": { + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + }, + "runtime": { + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll": { + "assemblyVersion": "15.0.0.0", + "fileVersion": "17.1200.24.56501" + }, + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll": { + "assemblyVersion": "15.0.0.0", + "fileVersion": "17.1200.24.56501" + }, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": { + "assemblyVersion": "15.0.0.0", + "fileVersion": "17.1200.24.56501" + } + }, + "resources": { + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "zh-Hant" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.TestPlatform.TestHost/17.12.0": { + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.12.0", + "Newtonsoft.Json": "13.0.1" + }, + "runtime": { + "lib/netcoreapp3.1/Microsoft.TestPlatform.CommunicationUtilities.dll": { + "assemblyVersion": "15.0.0.0", + "fileVersion": "17.1200.24.56501" + }, + "lib/netcoreapp3.1/Microsoft.TestPlatform.CrossPlatEngine.dll": { + "assemblyVersion": "15.0.0.0", + "fileVersion": "17.1200.24.56501" + }, + "lib/netcoreapp3.1/Microsoft.TestPlatform.Utilities.dll": { + "assemblyVersion": "15.0.0.0", + "fileVersion": "17.1200.24.56501" + }, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.Common.dll": { + "assemblyVersion": "15.0.0.0", + "fileVersion": "17.1200.24.56501" + }, + "lib/netcoreapp3.1/testhost.dll": { + "assemblyVersion": "15.0.0.0", + "fileVersion": "17.1200.24.56501" + } + }, + "resources": { + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "zh-Hant" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "zh-Hant" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Newtonsoft.Json/13.0.1": { + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.1.25517" + } + } + }, + "System.Reflection.Metadata/1.6.0": {}, + "xunit/2.9.2": { + "dependencies": { + "xunit.analyzers": "1.16.0", + "xunit.assert": "2.9.2", + "xunit.core": "2.9.2" + } + }, + "xunit.abstractions/2.0.3": { + "runtime": { + "lib/netstandard2.0/xunit.abstractions.dll": { + "assemblyVersion": "2.0.0.0", + "fileVersion": "2.0.0.0" + } + } + }, + "xunit.analyzers/1.16.0": {}, + "xunit.assert/2.9.2": { + "runtime": { + "lib/net6.0/xunit.assert.dll": { + "assemblyVersion": "2.9.2.0", + "fileVersion": "2.9.2.0" + } + } + }, + "xunit.core/2.9.2": { + "dependencies": { + "xunit.extensibility.core": "2.9.2", + "xunit.extensibility.execution": "2.9.2" + } + }, + "xunit.extensibility.core/2.9.2": { + "dependencies": { + "xunit.abstractions": "2.0.3" + }, + "runtime": { + "lib/netstandard1.1/xunit.core.dll": { + "assemblyVersion": "2.9.2.0", + "fileVersion": "2.9.2.0" + } + } + }, + "xunit.extensibility.execution/2.9.2": { + "dependencies": { + "xunit.extensibility.core": "2.9.2" + }, + "runtime": { + "lib/netstandard1.1/xunit.execution.dotnet.dll": { + "assemblyVersion": "2.9.2.0", + "fileVersion": "2.9.2.0" + } + } + }, + "xunit.runner.visualstudio/2.8.2": {}, + "PleasePayMe.Domain/1.0.0": { + "runtime": { + "PleasePayMe.Domain.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "PleasePayMe.Domain.Tests/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Microsoft.CodeCoverage/17.12.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==", + "path": "microsoft.codecoverage/17.12.0", + "hashPath": "microsoft.codecoverage.17.12.0.nupkg.sha512" + }, + "Microsoft.NET.Test.Sdk/17.12.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==", + "path": "microsoft.net.test.sdk/17.12.0", + "hashPath": "microsoft.net.test.sdk.17.12.0.nupkg.sha512" + }, + "Microsoft.TestPlatform.ObjectModel/17.12.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", + "path": "microsoft.testplatform.objectmodel/17.12.0", + "hashPath": "microsoft.testplatform.objectmodel.17.12.0.nupkg.sha512" + }, + "Microsoft.TestPlatform.TestHost/17.12.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==", + "path": "microsoft.testplatform.testhost/17.12.0", + "hashPath": "microsoft.testplatform.testhost.17.12.0.nupkg.sha512" + }, + "Newtonsoft.Json/13.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==", + "path": "newtonsoft.json/13.0.1", + "hashPath": "newtonsoft.json.13.0.1.nupkg.sha512" + }, + "System.Reflection.Metadata/1.6.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==", + "path": "system.reflection.metadata/1.6.0", + "hashPath": "system.reflection.metadata.1.6.0.nupkg.sha512" + }, + "xunit/2.9.2": { + "type": "package", + "serviceable": true, + "sha512": "sha512-7LhFS2N9Z6Xgg8aE5lY95cneYivRMfRI8v+4PATa4S64D5Z/Plkg0qa8dTRHSiGRgVZ/CL2gEfJDE5AUhOX+2Q==", + "path": "xunit/2.9.2", + "hashPath": "xunit.2.9.2.nupkg.sha512" + }, + "xunit.abstractions/2.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==", + "path": "xunit.abstractions/2.0.3", + "hashPath": "xunit.abstractions.2.0.3.nupkg.sha512" + }, + "xunit.analyzers/1.16.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-hptYM7vGr46GUIgZt21YHO4rfuBAQS2eINbFo16CV/Dqq+24Tp+P5gDCACu1AbFfW4Sp/WRfDPSK8fmUUb8s0Q==", + "path": "xunit.analyzers/1.16.0", + "hashPath": "xunit.analyzers.1.16.0.nupkg.sha512" + }, + "xunit.assert/2.9.2": { + "type": "package", + "serviceable": true, + "sha512": "sha512-QkNBAQG4pa66cholm28AxijBjrmki98/vsEh4Sx5iplzotvPgpiotcxqJQMRC8d7RV7nIT8ozh97957hDnZwsQ==", + "path": "xunit.assert/2.9.2", + "hashPath": "xunit.assert.2.9.2.nupkg.sha512" + }, + "xunit.core/2.9.2": { + "type": "package", + "serviceable": true, + "sha512": "sha512-O6RrNSdmZ0xgEn5kT927PNwog5vxTtKrWMihhhrT0Sg9jQ7iBDciYOwzBgP2krBEk5/GBXI18R1lKvmnxGcb4w==", + "path": "xunit.core/2.9.2", + "hashPath": "xunit.core.2.9.2.nupkg.sha512" + }, + "xunit.extensibility.core/2.9.2": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Ol+KlBJz1x8BrdnhN2DeOuLrr1I/cTwtHCggL9BvYqFuVd/TUSzxNT5O0NxCIXth30bsKxgMfdqLTcORtM52yQ==", + "path": "xunit.extensibility.core/2.9.2", + "hashPath": "xunit.extensibility.core.2.9.2.nupkg.sha512" + }, + "xunit.extensibility.execution/2.9.2": { + "type": "package", + "serviceable": true, + "sha512": "sha512-rKMpq4GsIUIJibXuZoZ8lYp5EpROlnYaRpwu9Zr0sRZXE7JqJfEEbCsUriZqB+ByXCLFBJyjkTRULMdC+U566g==", + "path": "xunit.extensibility.execution/2.9.2", + "hashPath": "xunit.extensibility.execution.2.9.2.nupkg.sha512" + }, + "xunit.runner.visualstudio/2.8.2": { + "type": "package", + "serviceable": true, + "sha512": "sha512-vm1tbfXhFmjFMUmS4M0J0ASXz3/U5XvXBa6DOQUL3fEz4Vt6YPhv+ESCarx6M6D+9kJkJYZKCNvJMas1+nVfmQ==", + "path": "xunit.runner.visualstudio/2.8.2", + "hashPath": "xunit.runner.visualstudio.2.8.2.nupkg.sha512" + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.dll new file mode 100644 index 0000000..8fbcbc1 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.pdb b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.pdb new file mode 100644 index 0000000..918b899 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.pdb differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.runtimeconfig.json b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.runtimeconfig.json new file mode 100644 index 0000000..b19c3c8 --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.Tests.runtimeconfig.json @@ -0,0 +1,12 @@ +{ + "runtimeOptions": { + "tfm": "net9.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "9.0.0" + }, + "configProperties": { + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..e1642b6 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..9c94240 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..6a22512 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..83c68f1 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..5f56087 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..87f03f5 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..252c548 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..c405cb0 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..0b8d7a2 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..8a40450 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..094510e Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..f2ea114 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..a4f9b88 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..1cedf43 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..50efa40 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..8609499 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..5d22850 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..6c3f966 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..1354d08 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..97f4c62 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..efeda12 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..ca6b10d Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..000757f Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..4cd8054 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..411d2b3 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..b13b763 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..af71519 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..7239741 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..5793f35 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..8cff118 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..41f33a3 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..a959dc6 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..89b460c Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..357c278 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..843f9bc Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..aaeb3f0 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..f158708 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..3cd44ff Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..cc17f24 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..56d009b Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..5bb5c00 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..ef229ea Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..6d333ed Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..de5092c Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..df016c7 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..87366d2 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..06c0baf Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..65bb33a Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..479e58e Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..7fb9358 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..f146cd3 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..11840ed Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/testhost.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/testhost.dll new file mode 100644 index 0000000..6023d9b Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/testhost.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/testhost.exe b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/testhost.exe new file mode 100644 index 0000000..79b1ef1 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/testhost.exe differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..66a78d2 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..a35ce7c Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..588f20e Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..204c223 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..cce1aac Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.abstractions.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.abstractions.dll new file mode 100644 index 0000000..d1e90bf Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.abstractions.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.assert.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.assert.dll new file mode 100644 index 0000000..8aa9a0e Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.assert.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.core.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.core.dll new file mode 100644 index 0000000..6c02a16 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.core.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.execution.dotnet.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.execution.dotnet.dll new file mode 100644 index 0000000..5613a34 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.execution.dotnet.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.runner.reporters.netcoreapp10.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.runner.reporters.netcoreapp10.dll new file mode 100644 index 0000000..ca76232 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.runner.reporters.netcoreapp10.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.runner.utility.netcoreapp10.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.runner.utility.netcoreapp10.dll new file mode 100644 index 0000000..c247a4e Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.runner.utility.netcoreapp10.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.runner.visualstudio.testadapter.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.runner.visualstudio.testadapter.dll new file mode 100644 index 0000000..2c4b812 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/xunit.runner.visualstudio.testadapter.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..fef8bc0 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..5a3ae3f Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..95e6929 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..ae8b0fb Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..af4c95a Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll new file mode 100644 index 0000000..bd707dd Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll new file mode 100644 index 0000000..9271175 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll new file mode 100644 index 0000000..b1e1340 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll new file mode 100644 index 0000000..577aa5a Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll new file mode 100644 index 0000000..c04de36 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/bin/Debug/net9.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll differ diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePa.5CA2F605.Up2Date b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePa.5CA2F605.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.AssemblyInfo.cs b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.AssemblyInfo.cs new file mode 100644 index 0000000..f28fd36 --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("PleasePayMe.Domain.Tests")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("PleasePayMe.Domain.Tests")] +[assembly: System.Reflection.AssemblyTitleAttribute("PleasePayMe.Domain.Tests")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.AssemblyInfoInputs.cache b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.AssemblyInfoInputs.cache new file mode 100644 index 0000000..5e49e47 --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +8a568fb0139a1bbe9ef42fe90d37e007ac02715958afff9ad342fd29e0adc3ee diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.GeneratedMSBuildEditorConfig.editorconfig b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..6b07d5d --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = PleasePayMe.Domain.Tests +build_property.ProjectDir = c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.GlobalUsings.g.cs b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.assets.cache b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.assets.cache new file mode 100644 index 0000000..266d29f Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.assets.cache differ diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.csproj.AssemblyReference.cache b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.csproj.AssemblyReference.cache new file mode 100644 index 0000000..5559239 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.csproj.AssemblyReference.cache differ diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.csproj.CoreCompileInputs.cache b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..b8e47fb --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +91d25081734e6f3f775ce3ac6e9e7ecf46c1cb750b2dbe0423f29909145077a2 diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.csproj.FileListAbsolute.txt b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..deca82c --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.csproj.FileListAbsolute.txt @@ -0,0 +1,101 @@ +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\.msCoverageSourceRootsMapping_PleasePayMe.Domain.Tests +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\PleasePayMe.Domain.Tests.csproj.AssemblyReference.cache +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\PleasePayMe.Domain.Tests.GeneratedMSBuildEditorConfig.editorconfig +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\PleasePayMe.Domain.Tests.AssemblyInfoInputs.cache +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\PleasePayMe.Domain.Tests.AssemblyInfo.cs +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\PleasePayMe.Domain.Tests.csproj.CoreCompileInputs.cache +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\testhost.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\testhost.exe +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\xunit.runner.visualstudio.testadapter.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\xunit.runner.reporters.netcoreapp10.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\xunit.runner.utility.netcoreapp10.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\PleasePayMe.Domain.Tests.deps.json +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\PleasePayMe.Domain.Tests.runtimeconfig.json +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\PleasePayMe.Domain.Tests.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\PleasePayMe.Domain.Tests.pdb +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\Microsoft.VisualStudio.CodeCoverage.Shim.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\Microsoft.TestPlatform.CoreUtilities.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\Microsoft.TestPlatform.PlatformAbstractions.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\Microsoft.TestPlatform.CommunicationUtilities.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\Microsoft.TestPlatform.CrossPlatEngine.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\Microsoft.TestPlatform.Utilities.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\Microsoft.VisualStudio.TestPlatform.Common.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\Newtonsoft.Json.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\xunit.abstractions.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\xunit.assert.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\xunit.core.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\xunit.execution.dotnet.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\cs\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\cs\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\de\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\de\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\es\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\es\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\fr\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\fr\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\it\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\it\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ja\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ja\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ko\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ko\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\pl\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\pl\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\pt-BR\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\pt-BR\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ru\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ru\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\tr\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\tr\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\zh-Hans\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\zh-Hans\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\zh-Hant\Microsoft.TestPlatform.CoreUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\zh-Hant\Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\cs\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\cs\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\cs\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\de\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\de\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\de\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\es\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\es\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\es\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\fr\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\fr\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\fr\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\it\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\it\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\it\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ja\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ja\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ja\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ko\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ko\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ko\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\pl\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\pl\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\pl\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\pt-BR\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\pt-BR\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\pt-BR\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ru\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ru\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\ru\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\tr\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\tr\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\tr\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\zh-Hans\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\zh-Hans\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\zh-Hans\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\zh-Hant\Microsoft.TestPlatform.CommunicationUtilities.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\zh-Hant\Microsoft.TestPlatform.CrossPlatEngine.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\zh-Hant\Microsoft.VisualStudio.TestPlatform.Common.resources.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\PleasePayMe.Domain.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\bin\Debug\net9.0\PleasePayMe.Domain.pdb +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\PleasePa.5CA2F605.Up2Date +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\PleasePayMe.Domain.Tests.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\refint\PleasePayMe.Domain.Tests.dll +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\PleasePayMe.Domain.Tests.pdb +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\PleasePayMe.Domain.Tests.genruntimeconfig.cache +c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain.Tests\obj\Debug\net9.0\ref\PleasePayMe.Domain.Tests.dll diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.dll b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.dll new file mode 100644 index 0000000..8fbcbc1 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.dll differ diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.genruntimeconfig.cache b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.genruntimeconfig.cache new file mode 100644 index 0000000..6a99f3c --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.genruntimeconfig.cache @@ -0,0 +1 @@ +0889da6ad50f52b71f1a011fb8e7f20af3cfccbda3575ae5ae2ff9256c86ca1f diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.pdb b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.pdb new file mode 100644 index 0000000..918b899 Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/PleasePayMe.Domain.Tests.pdb differ diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/ref/PleasePayMe.Domain.Tests.dll b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/ref/PleasePayMe.Domain.Tests.dll new file mode 100644 index 0000000..c7b461d Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/ref/PleasePayMe.Domain.Tests.dll differ diff --git a/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/refint/PleasePayMe.Domain.Tests.dll b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/refint/PleasePayMe.Domain.Tests.dll new file mode 100644 index 0000000..c7b461d Binary files /dev/null and b/src/PleasePayMe.Domain.Tests/obj/Debug/net9.0/refint/PleasePayMe.Domain.Tests.dll differ diff --git a/src/PleasePayMe.Domain.Tests/obj/PleasePayMe.Domain.Tests.csproj.nuget.dgspec.json b/src/PleasePayMe.Domain.Tests/obj/PleasePayMe.Domain.Tests.csproj.nuget.dgspec.json new file mode 100644 index 0000000..f7cdbc8 --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/PleasePayMe.Domain.Tests.csproj.nuget.dgspec.json @@ -0,0 +1,146 @@ +{ + "format": 1, + "restore": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain.Tests\\PleasePayMe.Domain.Tests.csproj": {} + }, + "projects": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain.Tests\\PleasePayMe.Domain.Tests.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain.Tests\\PleasePayMe.Domain.Tests.csproj", + "projectName": "PleasePayMe.Domain.Tests", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain.Tests\\PleasePayMe.Domain.Tests.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain.Tests\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "dependencies": { + "Microsoft.NET.Test.Sdk": { + "target": "Package", + "version": "[17.12.0, )" + }, + "xunit": { + "target": "Package", + "version": "[2.9.2, )" + }, + "xunit.runner.visualstudio": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[2.8.2, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "projectName": "PleasePayMe.Domain", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Domain.Tests/obj/PleasePayMe.Domain.Tests.csproj.nuget.g.props b/src/PleasePayMe.Domain.Tests/obj/PleasePayMe.Domain.Tests.csproj.nuget.g.props new file mode 100644 index 0000000..a7d0baa --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/PleasePayMe.Domain.Tests.csproj.nuget.g.props @@ -0,0 +1,25 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget + C:\Users\ggpo1\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget + PackageReference + 6.14.3 + + + + + + + + + + + + + C:\Users\ggpo1\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget\xunit.analyzers\1.16.0 + + \ No newline at end of file diff --git a/src/PleasePayMe.Domain.Tests/obj/PleasePayMe.Domain.Tests.csproj.nuget.g.targets b/src/PleasePayMe.Domain.Tests/obj/PleasePayMe.Domain.Tests.csproj.nuget.g.targets new file mode 100644 index 0000000..45db397 --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/PleasePayMe.Domain.Tests.csproj.nuget.g.targets @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/PleasePayMe.Domain.Tests/obj/project.assets.json b/src/PleasePayMe.Domain.Tests/obj/project.assets.json new file mode 100644 index 0000000..d112c6a --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/project.assets.json @@ -0,0 +1,966 @@ +{ + "version": 3, + "targets": { + "net9.0": { + "Microsoft.CodeCoverage/17.12.0": { + "type": "package", + "compile": { + "lib/netcoreapp3.1/Microsoft.VisualStudio.CodeCoverage.Shim.dll": {} + }, + "runtime": { + "lib/netcoreapp3.1/Microsoft.VisualStudio.CodeCoverage.Shim.dll": {} + }, + "build": { + "build/netstandard2.0/Microsoft.CodeCoverage.props": {}, + "build/netstandard2.0/Microsoft.CodeCoverage.targets": {} + } + }, + "Microsoft.NET.Test.Sdk/17.12.0": { + "type": "package", + "dependencies": { + "Microsoft.CodeCoverage": "17.12.0", + "Microsoft.TestPlatform.TestHost": "17.12.0" + }, + "compile": { + "lib/netcoreapp3.1/_._": {} + }, + "runtime": { + "lib/netcoreapp3.1/_._": {} + }, + "build": { + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.props": {}, + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.targets": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/Microsoft.NET.Test.Sdk.props": {} + } + }, + "Microsoft.TestPlatform.ObjectModel/17.12.0": { + "type": "package", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + }, + "compile": { + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {} + }, + "runtime": { + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {} + }, + "resource": { + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll": { + "locale": "zh-Hant" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.TestPlatform.TestHost/17.12.0": { + "type": "package", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.12.0", + "Newtonsoft.Json": "13.0.1" + }, + "compile": { + "lib/netcoreapp3.1/Microsoft.TestPlatform.CommunicationUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.CrossPlatEngine.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.Utilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.Common.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {}, + "lib/netcoreapp3.1/testhost.dll": { + "related": ".deps.json" + } + }, + "runtime": { + "lib/netcoreapp3.1/Microsoft.TestPlatform.CommunicationUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.CrossPlatEngine.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll": {}, + "lib/netcoreapp3.1/Microsoft.TestPlatform.Utilities.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.Common.dll": {}, + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {}, + "lib/netcoreapp3.1/testhost.dll": { + "related": ".deps.json" + } + }, + "resource": { + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "cs" + }, + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "de" + }, + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "es" + }, + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "fr" + }, + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "it" + }, + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "ja" + }, + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "ko" + }, + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "pl" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "pt-BR" + }, + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "ru" + }, + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "tr" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": { + "locale": "zh-Hant" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": { + "locale": "zh-Hant" + }, + "lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": { + "locale": "zh-Hant" + } + }, + "build": { + "build/netcoreapp3.1/Microsoft.TestPlatform.TestHost.props": {} + } + }, + "Newtonsoft.Json/13.0.1": { + "type": "package", + "compile": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/Newtonsoft.Json.dll": { + "related": ".xml" + } + } + }, + "System.Reflection.Metadata/1.6.0": { + "type": "package", + "compile": { + "lib/netstandard2.0/System.Reflection.Metadata.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/System.Reflection.Metadata.dll": { + "related": ".xml" + } + } + }, + "xunit/2.9.2": { + "type": "package", + "dependencies": { + "xunit.analyzers": "1.16.0", + "xunit.assert": "2.9.2", + "xunit.core": "[2.9.2]" + } + }, + "xunit.abstractions/2.0.3": { + "type": "package", + "compile": { + "lib/netstandard2.0/xunit.abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.0/xunit.abstractions.dll": { + "related": ".xml" + } + } + }, + "xunit.analyzers/1.16.0": { + "type": "package" + }, + "xunit.assert/2.9.2": { + "type": "package", + "compile": { + "lib/net6.0/xunit.assert.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/xunit.assert.dll": { + "related": ".xml" + } + } + }, + "xunit.core/2.9.2": { + "type": "package", + "dependencies": { + "xunit.extensibility.core": "[2.9.2]", + "xunit.extensibility.execution": "[2.9.2]" + }, + "build": { + "build/xunit.core.props": {}, + "build/xunit.core.targets": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/xunit.core.props": {}, + "buildMultiTargeting/xunit.core.targets": {} + } + }, + "xunit.extensibility.core/2.9.2": { + "type": "package", + "dependencies": { + "xunit.abstractions": "2.0.3" + }, + "compile": { + "lib/netstandard1.1/xunit.core.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard1.1/xunit.core.dll": { + "related": ".xml" + } + } + }, + "xunit.extensibility.execution/2.9.2": { + "type": "package", + "dependencies": { + "xunit.extensibility.core": "[2.9.2]" + }, + "compile": { + "lib/netstandard1.1/xunit.execution.dotnet.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard1.1/xunit.execution.dotnet.dll": { + "related": ".xml" + } + } + }, + "xunit.runner.visualstudio/2.8.2": { + "type": "package", + "compile": { + "lib/net6.0/_._": {} + }, + "runtime": { + "lib/net6.0/_._": {} + }, + "build": { + "build/net6.0/xunit.runner.visualstudio.props": {} + } + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v9.0", + "compile": { + "bin/placeholder/PleasePayMe.Domain.dll": {} + }, + "runtime": { + "bin/placeholder/PleasePayMe.Domain.dll": {} + } + } + } + }, + "libraries": { + "Microsoft.CodeCoverage/17.12.0": { + "sha512": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==", + "type": "package", + "path": "microsoft.codecoverage/17.12.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "build/netstandard2.0/CodeCoverage/CodeCoverage.config", + "build/netstandard2.0/CodeCoverage/CodeCoverage.exe", + "build/netstandard2.0/CodeCoverage/Cov_x86.config", + "build/netstandard2.0/CodeCoverage/amd64/CodeCoverage.exe", + "build/netstandard2.0/CodeCoverage/amd64/Cov_x64.config", + "build/netstandard2.0/CodeCoverage/amd64/covrun64.dll", + "build/netstandard2.0/CodeCoverage/amd64/msdia140.dll", + "build/netstandard2.0/CodeCoverage/arm64/Cov_arm64.config", + "build/netstandard2.0/CodeCoverage/arm64/covrunarm64.dll", + "build/netstandard2.0/CodeCoverage/arm64/msdia140.dll", + "build/netstandard2.0/CodeCoverage/codecoveragemessages.dll", + "build/netstandard2.0/CodeCoverage/coreclr/Microsoft.VisualStudio.CodeCoverage.Shim.dll", + "build/netstandard2.0/CodeCoverage/covrun32.dll", + "build/netstandard2.0/CodeCoverage/msdia140.dll", + "build/netstandard2.0/Microsoft.CodeCoverage.Core.dll", + "build/netstandard2.0/Microsoft.CodeCoverage.Instrumentation.Core.dll", + "build/netstandard2.0/Microsoft.CodeCoverage.Instrumentation.dll", + "build/netstandard2.0/Microsoft.CodeCoverage.Interprocess.dll", + "build/netstandard2.0/Microsoft.CodeCoverage.props", + "build/netstandard2.0/Microsoft.CodeCoverage.targets", + "build/netstandard2.0/Microsoft.DiaSymReader.dll", + "build/netstandard2.0/Microsoft.VisualStudio.TraceDataCollector.dll", + "build/netstandard2.0/Mono.Cecil.Pdb.dll", + "build/netstandard2.0/Mono.Cecil.Rocks.dll", + "build/netstandard2.0/Mono.Cecil.dll", + "build/netstandard2.0/ThirdPartyNotices.txt", + "build/netstandard2.0/alpine/x64/Cov_x64.config", + "build/netstandard2.0/alpine/x64/libCoverageInstrumentationMethod.so", + "build/netstandard2.0/alpine/x64/libInstrumentationEngine.so", + "build/netstandard2.0/arm64/MicrosoftInstrumentationEngine_arm64.dll", + "build/netstandard2.0/cs/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/de/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/es/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/fr/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/it/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/ja/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/ko/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/macos/x64/Cov_x64.config", + "build/netstandard2.0/macos/x64/libCoverageInstrumentationMethod.dylib", + "build/netstandard2.0/macos/x64/libInstrumentationEngine.dylib", + "build/netstandard2.0/pl/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/pt-BR/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/ru/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/tr/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/ubuntu/x64/Cov_x64.config", + "build/netstandard2.0/ubuntu/x64/libCoverageInstrumentationMethod.so", + "build/netstandard2.0/ubuntu/x64/libInstrumentationEngine.so", + "build/netstandard2.0/x64/MicrosoftInstrumentationEngine_x64.dll", + "build/netstandard2.0/x86/MicrosoftInstrumentationEngine_x86.dll", + "build/netstandard2.0/zh-Hans/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "build/netstandard2.0/zh-Hant/Microsoft.VisualStudio.TraceDataCollector.resources.dll", + "lib/net462/Microsoft.VisualStudio.CodeCoverage.Shim.dll", + "lib/netcoreapp3.1/Microsoft.VisualStudio.CodeCoverage.Shim.dll", + "microsoft.codecoverage.17.12.0.nupkg.sha512", + "microsoft.codecoverage.nuspec" + ] + }, + "Microsoft.NET.Test.Sdk/17.12.0": { + "sha512": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==", + "type": "package", + "path": "microsoft.net.test.sdk/17.12.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "build/net462/Microsoft.NET.Test.Sdk.props", + "build/net462/Microsoft.NET.Test.Sdk.targets", + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.Program.cs", + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.Program.fs", + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.Program.vb", + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.props", + "build/netcoreapp3.1/Microsoft.NET.Test.Sdk.targets", + "buildMultiTargeting/Microsoft.NET.Test.Sdk.props", + "lib/net462/_._", + "lib/netcoreapp3.1/_._", + "microsoft.net.test.sdk.17.12.0.nupkg.sha512", + "microsoft.net.test.sdk.nuspec" + ] + }, + "Microsoft.TestPlatform.ObjectModel/17.12.0": { + "sha512": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", + "type": "package", + "path": "microsoft.testplatform.objectmodel/17.12.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "lib/net462/Microsoft.TestPlatform.CoreUtilities.dll", + "lib/net462/Microsoft.TestPlatform.PlatformAbstractions.dll", + "lib/net462/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll", + "lib/net462/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/de/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/es/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/it/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/net462/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/net462/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll", + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll", + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/Microsoft.TestPlatform.CoreUtilities.dll", + "lib/netstandard2.0/Microsoft.TestPlatform.PlatformAbstractions.dll", + "lib/netstandard2.0/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll", + "lib/netstandard2.0/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/de/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/es/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/it/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll", + "microsoft.testplatform.objectmodel.17.12.0.nupkg.sha512", + "microsoft.testplatform.objectmodel.nuspec" + ] + }, + "Microsoft.TestPlatform.TestHost/17.12.0": { + "sha512": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==", + "type": "package", + "path": "microsoft.testplatform.testhost/17.12.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "build/netcoreapp3.1/Microsoft.TestPlatform.TestHost.props", + "build/netcoreapp3.1/x64/testhost.dll", + "build/netcoreapp3.1/x64/testhost.exe", + "build/netcoreapp3.1/x86/testhost.x86.dll", + "build/netcoreapp3.1/x86/testhost.x86.exe", + "lib/net462/_._", + "lib/netcoreapp3.1/Microsoft.TestPlatform.CommunicationUtilities.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.CrossPlatEngine.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll", + "lib/netcoreapp3.1/Microsoft.TestPlatform.Utilities.dll", + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.Common.dll", + "lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll", + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/testhost.deps.json", + "lib/netcoreapp3.1/testhost.dll", + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/x64/msdia140.dll", + "lib/netcoreapp3.1/x86/msdia140.dll", + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll", + "lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll", + "lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll", + "microsoft.testplatform.testhost.17.12.0.nupkg.sha512", + "microsoft.testplatform.testhost.nuspec" + ] + }, + "Newtonsoft.Json/13.0.1": { + "sha512": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==", + "type": "package", + "path": "newtonsoft.json/13.0.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.md", + "lib/net20/Newtonsoft.Json.dll", + "lib/net20/Newtonsoft.Json.xml", + "lib/net35/Newtonsoft.Json.dll", + "lib/net35/Newtonsoft.Json.xml", + "lib/net40/Newtonsoft.Json.dll", + "lib/net40/Newtonsoft.Json.xml", + "lib/net45/Newtonsoft.Json.dll", + "lib/net45/Newtonsoft.Json.xml", + "lib/netstandard1.0/Newtonsoft.Json.dll", + "lib/netstandard1.0/Newtonsoft.Json.xml", + "lib/netstandard1.3/Newtonsoft.Json.dll", + "lib/netstandard1.3/Newtonsoft.Json.xml", + "lib/netstandard2.0/Newtonsoft.Json.dll", + "lib/netstandard2.0/Newtonsoft.Json.xml", + "newtonsoft.json.13.0.1.nupkg.sha512", + "newtonsoft.json.nuspec", + "packageIcon.png" + ] + }, + "System.Reflection.Metadata/1.6.0": { + "sha512": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==", + "type": "package", + "path": "system.reflection.metadata/1.6.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/netstandard1.1/System.Reflection.Metadata.dll", + "lib/netstandard1.1/System.Reflection.Metadata.xml", + "lib/netstandard2.0/System.Reflection.Metadata.dll", + "lib/netstandard2.0/System.Reflection.Metadata.xml", + "lib/portable-net45+win8/System.Reflection.Metadata.dll", + "lib/portable-net45+win8/System.Reflection.Metadata.xml", + "system.reflection.metadata.1.6.0.nupkg.sha512", + "system.reflection.metadata.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "xunit/2.9.2": { + "sha512": "7LhFS2N9Z6Xgg8aE5lY95cneYivRMfRI8v+4PATa4S64D5Z/Plkg0qa8dTRHSiGRgVZ/CL2gEfJDE5AUhOX+2Q==", + "type": "package", + "path": "xunit/2.9.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "xunit.2.9.2.nupkg.sha512", + "xunit.nuspec" + ] + }, + "xunit.abstractions/2.0.3": { + "sha512": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==", + "type": "package", + "path": "xunit.abstractions/2.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "lib/net35/xunit.abstractions.dll", + "lib/net35/xunit.abstractions.xml", + "lib/netstandard1.0/xunit.abstractions.dll", + "lib/netstandard1.0/xunit.abstractions.xml", + "lib/netstandard2.0/xunit.abstractions.dll", + "lib/netstandard2.0/xunit.abstractions.xml", + "xunit.abstractions.2.0.3.nupkg.sha512", + "xunit.abstractions.nuspec" + ] + }, + "xunit.analyzers/1.16.0": { + "sha512": "hptYM7vGr46GUIgZt21YHO4rfuBAQS2eINbFo16CV/Dqq+24Tp+P5gDCACu1AbFfW4Sp/WRfDPSK8fmUUb8s0Q==", + "type": "package", + "path": "xunit.analyzers/1.16.0", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "analyzers/dotnet/cs/xunit.analyzers.dll", + "analyzers/dotnet/cs/xunit.analyzers.fixes.dll", + "tools/install.ps1", + "tools/uninstall.ps1", + "xunit.analyzers.1.16.0.nupkg.sha512", + "xunit.analyzers.nuspec" + ] + }, + "xunit.assert/2.9.2": { + "sha512": "QkNBAQG4pa66cholm28AxijBjrmki98/vsEh4Sx5iplzotvPgpiotcxqJQMRC8d7RV7nIT8ozh97957hDnZwsQ==", + "type": "package", + "path": "xunit.assert/2.9.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "lib/net6.0/xunit.assert.dll", + "lib/net6.0/xunit.assert.xml", + "lib/netstandard1.1/xunit.assert.dll", + "lib/netstandard1.1/xunit.assert.xml", + "xunit.assert.2.9.2.nupkg.sha512", + "xunit.assert.nuspec" + ] + }, + "xunit.core/2.9.2": { + "sha512": "O6RrNSdmZ0xgEn5kT927PNwog5vxTtKrWMihhhrT0Sg9jQ7iBDciYOwzBgP2krBEk5/GBXI18R1lKvmnxGcb4w==", + "type": "package", + "path": "xunit.core/2.9.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "build/xunit.core.props", + "build/xunit.core.targets", + "buildMultiTargeting/xunit.core.props", + "buildMultiTargeting/xunit.core.targets", + "xunit.core.2.9.2.nupkg.sha512", + "xunit.core.nuspec" + ] + }, + "xunit.extensibility.core/2.9.2": { + "sha512": "Ol+KlBJz1x8BrdnhN2DeOuLrr1I/cTwtHCggL9BvYqFuVd/TUSzxNT5O0NxCIXth30bsKxgMfdqLTcORtM52yQ==", + "type": "package", + "path": "xunit.extensibility.core/2.9.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "lib/net452/xunit.core.dll", + "lib/net452/xunit.core.dll.tdnet", + "lib/net452/xunit.core.xml", + "lib/net452/xunit.runner.tdnet.dll", + "lib/net452/xunit.runner.utility.net452.dll", + "lib/netstandard1.1/xunit.core.dll", + "lib/netstandard1.1/xunit.core.xml", + "xunit.extensibility.core.2.9.2.nupkg.sha512", + "xunit.extensibility.core.nuspec" + ] + }, + "xunit.extensibility.execution/2.9.2": { + "sha512": "rKMpq4GsIUIJibXuZoZ8lYp5EpROlnYaRpwu9Zr0sRZXE7JqJfEEbCsUriZqB+ByXCLFBJyjkTRULMdC+U566g==", + "type": "package", + "path": "xunit.extensibility.execution/2.9.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "lib/net452/xunit.execution.desktop.dll", + "lib/net452/xunit.execution.desktop.xml", + "lib/netstandard1.1/xunit.execution.dotnet.dll", + "lib/netstandard1.1/xunit.execution.dotnet.xml", + "xunit.extensibility.execution.2.9.2.nupkg.sha512", + "xunit.extensibility.execution.nuspec" + ] + }, + "xunit.runner.visualstudio/2.8.2": { + "sha512": "vm1tbfXhFmjFMUmS4M0J0ASXz3/U5XvXBa6DOQUL3fEz4Vt6YPhv+ESCarx6M6D+9kJkJYZKCNvJMas1+nVfmQ==", + "type": "package", + "path": "xunit.runner.visualstudio/2.8.2", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "_content/README.md", + "_content/logo-128-transparent.png", + "build/net462/xunit.abstractions.dll", + "build/net462/xunit.runner.reporters.net452.dll", + "build/net462/xunit.runner.utility.net452.dll", + "build/net462/xunit.runner.visualstudio.props", + "build/net462/xunit.runner.visualstudio.testadapter.dll", + "build/net6.0/xunit.abstractions.dll", + "build/net6.0/xunit.runner.reporters.netcoreapp10.dll", + "build/net6.0/xunit.runner.utility.netcoreapp10.dll", + "build/net6.0/xunit.runner.visualstudio.props", + "build/net6.0/xunit.runner.visualstudio.testadapter.dll", + "lib/net462/_._", + "lib/net6.0/_._", + "xunit.runner.visualstudio.2.8.2.nupkg.sha512", + "xunit.runner.visualstudio.nuspec" + ] + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "path": "../PleasePayMe.Domain/PleasePayMe.Domain.csproj", + "msbuildProject": "../PleasePayMe.Domain/PleasePayMe.Domain.csproj" + } + }, + "projectFileDependencyGroups": { + "net9.0": [ + "Microsoft.NET.Test.Sdk >= 17.12.0", + "PleasePayMe.Domain >= 1.0.0", + "xunit >= 2.9.2", + "xunit.runner.visualstudio >= 2.8.2" + ] + }, + "packageFolders": { + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain.Tests\\PleasePayMe.Domain.Tests.csproj", + "projectName": "PleasePayMe.Domain.Tests", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain.Tests\\PleasePayMe.Domain.Tests.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain.Tests\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "dependencies": { + "Microsoft.NET.Test.Sdk": { + "target": "Package", + "version": "[17.12.0, )" + }, + "xunit": { + "target": "Package", + "version": "[2.9.2, )" + }, + "xunit.runner.visualstudio": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[2.8.2, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Domain.Tests/obj/project.nuget.cache b/src/PleasePayMe.Domain.Tests/obj/project.nuget.cache new file mode 100644 index 0000000..b6cace8 --- /dev/null +++ b/src/PleasePayMe.Domain.Tests/obj/project.nuget.cache @@ -0,0 +1,23 @@ +{ + "version": 2, + "dgSpecHash": "y5ZydqH2BnY=", + "success": true, + "projectFilePath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain.Tests\\PleasePayMe.Domain.Tests.csproj", + "expectedPackageFiles": [ + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codecoverage\\17.12.0\\microsoft.codecoverage.17.12.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.net.test.sdk\\17.12.0\\microsoft.net.test.sdk.17.12.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.testplatform.objectmodel\\17.12.0\\microsoft.testplatform.objectmodel.17.12.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.testplatform.testhost\\17.12.0\\microsoft.testplatform.testhost.17.12.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\newtonsoft.json\\13.0.1\\newtonsoft.json.13.0.1.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.reflection.metadata\\1.6.0\\system.reflection.metadata.1.6.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\xunit\\2.9.2\\xunit.2.9.2.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\xunit.abstractions\\2.0.3\\xunit.abstractions.2.0.3.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\xunit.analyzers\\1.16.0\\xunit.analyzers.1.16.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\xunit.assert\\2.9.2\\xunit.assert.2.9.2.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\xunit.core\\2.9.2\\xunit.core.2.9.2.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\xunit.extensibility.core\\2.9.2\\xunit.extensibility.core.2.9.2.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\xunit.extensibility.execution\\2.9.2\\xunit.extensibility.execution.2.9.2.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\xunit.runner.visualstudio\\2.8.2\\xunit.runner.visualstudio.2.8.2.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/src/PleasePayMe.Domain/DomainException.cs b/src/PleasePayMe.Domain/DomainException.cs new file mode 100644 index 0000000..d2c564d --- /dev/null +++ b/src/PleasePayMe.Domain/DomainException.cs @@ -0,0 +1,8 @@ +namespace PleasePayMe.Domain; + +public sealed class DomainException : Exception +{ + public DomainException(string message) : base(message) + { + } +} diff --git a/src/PleasePayMe.Domain/Entities/Budget.cs b/src/PleasePayMe.Domain/Entities/Budget.cs new file mode 100644 index 0000000..badeb4e --- /dev/null +++ b/src/PleasePayMe.Domain/Entities/Budget.cs @@ -0,0 +1,17 @@ +namespace PleasePayMe.Domain.Entities; + +public sealed class Budget +{ + public long Id { get; set; } + public long UserId { get; set; } + public string Name { get; set; } = "Бюджет"; + public decimal TotalAmount { get; set; } + public DateOnly StartDate { get; set; } + public DateOnly EndDate { get; set; } + public string Currency { get; set; } = "RUB"; + public bool IsActive { get; set; } = true; + public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; + + public User User { get; set; } = null!; + public ICollection Expenses { get; set; } = new List(); +} diff --git a/src/PleasePayMe.Domain/Entities/Expense.cs b/src/PleasePayMe.Domain/Entities/Expense.cs new file mode 100644 index 0000000..602d251 --- /dev/null +++ b/src/PleasePayMe.Domain/Entities/Expense.cs @@ -0,0 +1,15 @@ +namespace PleasePayMe.Domain.Entities; + +public sealed class Expense +{ + public long Id { get; set; } + public long UserId { get; set; } + public long BudgetId { get; set; } + public decimal Amount { get; set; } + public string? Note { get; set; } + public DateOnly SpentAt { get; set; } + public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; + + public User User { get; set; } = null!; + public Budget Budget { get; set; } = null!; +} diff --git a/src/PleasePayMe.Domain/Entities/Job.cs b/src/PleasePayMe.Domain/Entities/Job.cs new file mode 100644 index 0000000..b222f57 --- /dev/null +++ b/src/PleasePayMe.Domain/Entities/Job.cs @@ -0,0 +1,21 @@ +using PleasePayMe.Domain; + +namespace PleasePayMe.Domain.Entities; + +public sealed class Job +{ + public long Id { get; set; } + public long UserId { get; set; } + public string Name { get; set; } = ""; + public decimal SalaryAmount { get; set; } + public string Currency { get; set; } = "RUB"; + /// 1–2 days of month when salary is paid (1–31). + public List PayDays { get; set; } = new(); + /// Percent of salary for the earlier pay day (0–100). Second day gets the rest. + public decimal FirstPayPercent { get; set; } = 50m; + public WeekendPayPolicy WeekendPolicy { get; set; } = WeekendPayPolicy.BeforeWeekend; + public bool IsActive { get; set; } = true; + public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; + + public User User { get; set; } = null!; +} diff --git a/src/PleasePayMe.Domain/Entities/TelegramLinkChallenge.cs b/src/PleasePayMe.Domain/Entities/TelegramLinkChallenge.cs new file mode 100644 index 0000000..8c2c7e4 --- /dev/null +++ b/src/PleasePayMe.Domain/Entities/TelegramLinkChallenge.cs @@ -0,0 +1,14 @@ +namespace PleasePayMe.Domain.Entities; + +/// +/// One-time token the bot puts in the cabinet URL so a Yandex login can +/// attach this Telegram user to the resulting Yandex identity. +/// +public sealed class TelegramLinkChallenge +{ + public string Token { get; set; } = ""; + public long TelegramUserId { get; set; } + public DateTimeOffset ExpiresAt { get; set; } + public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; + public DateTimeOffset? ConsumedAt { get; set; } +} diff --git a/src/PleasePayMe.Domain/Entities/TelegramYandexLink.cs b/src/PleasePayMe.Domain/Entities/TelegramYandexLink.cs new file mode 100644 index 0000000..f8891b3 --- /dev/null +++ b/src/PleasePayMe.Domain/Entities/TelegramYandexLink.cs @@ -0,0 +1,12 @@ +namespace PleasePayMe.Domain.Entities; + +/// +/// Maps a Telegram account to the Yandex-namespaced users.user_id +/// used by the cabinet and mobile app. +/// +public sealed class TelegramYandexLink +{ + public long TelegramUserId { get; set; } + public long YandexUserId { get; set; } + public DateTimeOffset LinkedAt { get; set; } = DateTimeOffset.UtcNow; +} diff --git a/src/PleasePayMe.Domain/Entities/User.cs b/src/PleasePayMe.Domain/Entities/User.cs new file mode 100644 index 0000000..4cf2485 --- /dev/null +++ b/src/PleasePayMe.Domain/Entities/User.cs @@ -0,0 +1,13 @@ +namespace PleasePayMe.Domain.Entities; + +public sealed class User +{ + public long UserId { get; set; } + public long? SelectedBudgetId { get; set; } + public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; + + public Budget? SelectedBudget { get; set; } + public ICollection Budgets { get; set; } = new List(); + public ICollection Expenses { get; set; } = new List(); + public ICollection Jobs { get; set; } = new List(); +} diff --git a/src/PleasePayMe.Domain/OAuthRedirectAllowlist.cs b/src/PleasePayMe.Domain/OAuthRedirectAllowlist.cs new file mode 100644 index 0000000..fd0811e --- /dev/null +++ b/src/PleasePayMe.Domain/OAuthRedirectAllowlist.cs @@ -0,0 +1,59 @@ +namespace PleasePayMe.Domain; + +/// +/// Exact-match allowlist for OAuth redirect_uri. Clients pick the URI +/// they registered with the provider; the API refuses anything else so a stolen +/// authorization code cannot be redeemed against an attacker-controlled callback. +/// +public static class OAuthRedirectAllowlist +{ + public static string Normalize(string uri) + { + if (!Uri.TryCreate(uri.Trim(), UriKind.Absolute, out var parsed)) + { + return uri.Trim(); + } + + var builder = new UriBuilder(parsed) + { + Host = parsed.Host.ToLowerInvariant(), + Fragment = string.Empty, + Query = string.Empty, + }; + + if (builder.Path.Length > 1) + { + builder.Path = builder.Path.TrimEnd('/'); + } + + return builder.Uri.GetLeftPart(UriPartial.Path); + } + + public static bool IsSafeHttpRedirect(string uri) + => Uri.TryCreate(uri.Trim(), UriKind.Absolute, out var parsed) + && (parsed.Scheme == Uri.UriSchemeHttps || parsed.Scheme == Uri.UriSchemeHttp); + + public static bool Contains(IEnumerable allowlist, string candidate) + { + if (!IsSafeHttpRedirect(candidate)) + { + return false; + } + + var normalized = Normalize(candidate); + return allowlist + .Where(item => !string.IsNullOrWhiteSpace(item)) + .Select(Normalize) + .Contains(normalized, StringComparer.Ordinal); + } + + public static IReadOnlyList Parse(params string?[] chunks) + { + return chunks + .Where(chunk => !string.IsNullOrWhiteSpace(chunk)) + .SelectMany(chunk => chunk!.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + .Where(item => IsSafeHttpRedirect(item)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + } +} diff --git a/src/PleasePayMe.Domain/PleasePayMe.Domain.csproj b/src/PleasePayMe.Domain/PleasePayMe.Domain.csproj new file mode 100644 index 0000000..125f4c9 --- /dev/null +++ b/src/PleasePayMe.Domain/PleasePayMe.Domain.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + + diff --git a/src/PleasePayMe.Domain/WeekendPayPolicy.cs b/src/PleasePayMe.Domain/WeekendPayPolicy.cs new file mode 100644 index 0000000..3ae5eae --- /dev/null +++ b/src/PleasePayMe.Domain/WeekendPayPolicy.cs @@ -0,0 +1,9 @@ +namespace PleasePayMe.Domain; + +public enum WeekendPayPolicy +{ + /// If nominal day is Sat/Sun, pay on the previous Friday. + BeforeWeekend = 0, + /// If nominal day is Sat/Sun, pay on the following Monday. + AfterWeekend = 1, +} diff --git a/src/PleasePayMe.Domain/YandexIdentity.cs b/src/PleasePayMe.Domain/YandexIdentity.cs new file mode 100644 index 0000000..62916cc --- /dev/null +++ b/src/PleasePayMe.Domain/YandexIdentity.cs @@ -0,0 +1,29 @@ +namespace PleasePayMe.Domain; + +/// +/// Maps a Yandex account id onto the same users.user_id space as Telegram. +/// The high bit stays below JavaScript Number.MAX_SAFE_INTEGER (2^53 − 1) +/// so the web cabinet and Flutter JSON stay exact. +/// +public static class YandexIdentity +{ + public const long NamespaceBit = 1L << 50; + + public static long ToInternalUserId(long yandexId) + { + if (yandexId <= 0) + { + throw new DomainException("Yandex user id must be positive"); + } + + if (yandexId >= NamespaceBit) + { + throw new DomainException("Yandex user id is out of range"); + } + + return NamespaceBit | yandexId; + } + + public static bool IsYandexUserId(long userId) => + userId > 0 && (userId & NamespaceBit) == NamespaceBit; +} diff --git a/src/PleasePayMe.Domain/bin/Debug/net9.0/PleasePayMe.Domain.deps.json b/src/PleasePayMe.Domain/bin/Debug/net9.0/PleasePayMe.Domain.deps.json new file mode 100644 index 0000000..43ee893 --- /dev/null +++ b/src/PleasePayMe.Domain/bin/Debug/net9.0/PleasePayMe.Domain.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v9.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v9.0": { + "PleasePayMe.Domain/1.0.0": { + "runtime": { + "PleasePayMe.Domain.dll": {} + } + } + } + }, + "libraries": { + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Domain/bin/Debug/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Domain/bin/Debug/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..e1642b6 Binary files /dev/null and b/src/PleasePayMe.Domain/bin/Debug/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Domain/bin/Debug/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Domain/bin/Debug/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..9c94240 Binary files /dev/null and b/src/PleasePayMe.Domain/bin/Debug/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Domain/bin/Release/net9.0/PleasePayMe.Domain.deps.json b/src/PleasePayMe.Domain/bin/Release/net9.0/PleasePayMe.Domain.deps.json new file mode 100644 index 0000000..43ee893 --- /dev/null +++ b/src/PleasePayMe.Domain/bin/Release/net9.0/PleasePayMe.Domain.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v9.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v9.0": { + "PleasePayMe.Domain/1.0.0": { + "runtime": { + "PleasePayMe.Domain.dll": {} + } + } + } + }, + "libraries": { + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Domain/bin/Release/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Domain/bin/Release/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..0c885d2 Binary files /dev/null and b/src/PleasePayMe.Domain/bin/Release/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Domain/bin/Release/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Domain/bin/Release/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..db59d49 Binary files /dev/null and b/src/PleasePayMe.Domain/bin/Release/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/src/PleasePayMe.Domain/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.AssemblyInfo.cs b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.AssemblyInfo.cs new file mode 100644 index 0000000..f89f14a --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("PleasePayMe.Domain")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("PleasePayMe.Domain")] +[assembly: System.Reflection.AssemblyTitleAttribute("PleasePayMe.Domain")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.AssemblyInfoInputs.cache b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.AssemblyInfoInputs.cache new file mode 100644 index 0000000..7f51542 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +41bfdd638ae805bc1c9f77eab74b0b6a1fabd7bd8f4d6b1eb07349c393b73bfd diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.GeneratedMSBuildEditorConfig.editorconfig b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..2c02340 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = PleasePayMe.Domain +build_property.ProjectDir = c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.GlobalUsings.g.cs b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.assets.cache b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.assets.cache new file mode 100644 index 0000000..2269d47 Binary files /dev/null and b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.assets.cache differ diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.csproj.CoreCompileInputs.cache b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..1e4dcd2 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +3bdbb7710cd3cc1ffb8886183aa7e926778b0e6ce377818d9d533434afa4b921 diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.csproj.FileListAbsolute.txt b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..383ed70 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.csproj.FileListAbsolute.txt @@ -0,0 +1,11 @@ +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\bin\Debug\net9.0\PleasePayMe.Domain.deps.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\bin\Debug\net9.0\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\bin\Debug\net9.0\PleasePayMe.Domain.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Debug\net9.0\PleasePayMe.Domain.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Debug\net9.0\PleasePayMe.Domain.AssemblyInfoInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Debug\net9.0\PleasePayMe.Domain.AssemblyInfo.cs +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Debug\net9.0\PleasePayMe.Domain.csproj.CoreCompileInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Debug\net9.0\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Debug\net9.0\refint\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Debug\net9.0\PleasePayMe.Domain.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Debug\net9.0\ref\PleasePayMe.Domain.dll diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..e1642b6 Binary files /dev/null and b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..9c94240 Binary files /dev/null and b/src/PleasePayMe.Domain/obj/Debug/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/ref/PleasePayMe.Domain.dll b/src/PleasePayMe.Domain/obj/Debug/net9.0/ref/PleasePayMe.Domain.dll new file mode 100644 index 0000000..45a7f0c Binary files /dev/null and b/src/PleasePayMe.Domain/obj/Debug/net9.0/ref/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Domain/obj/Debug/net9.0/refint/PleasePayMe.Domain.dll b/src/PleasePayMe.Domain/obj/Debug/net9.0/refint/PleasePayMe.Domain.dll new file mode 100644 index 0000000..45a7f0c Binary files /dev/null and b/src/PleasePayMe.Domain/obj/Debug/net9.0/refint/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Domain/obj/PleasePayMe.Domain.csproj.nuget.dgspec.json b/src/PleasePayMe.Domain/obj/PleasePayMe.Domain.csproj.nuget.dgspec.json new file mode 100644 index 0000000..72e2c3b --- /dev/null +++ b/src/PleasePayMe.Domain/obj/PleasePayMe.Domain.csproj.nuget.dgspec.json @@ -0,0 +1,67 @@ +{ + "format": 1, + "restore": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": {} + }, + "projects": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "projectName": "PleasePayMe.Domain", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Domain/obj/PleasePayMe.Domain.csproj.nuget.g.props b/src/PleasePayMe.Domain/obj/PleasePayMe.Domain.csproj.nuget.g.props new file mode 100644 index 0000000..d608f0f --- /dev/null +++ b/src/PleasePayMe.Domain/obj/PleasePayMe.Domain.csproj.nuget.g.props @@ -0,0 +1,15 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget + C:\Users\ggpo1\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget + PackageReference + 6.14.3 + + + + + \ No newline at end of file diff --git a/src/PleasePayMe.Domain/obj/PleasePayMe.Domain.csproj.nuget.g.targets b/src/PleasePayMe.Domain/obj/PleasePayMe.Domain.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/src/PleasePayMe.Domain/obj/PleasePayMe.Domain.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/src/PleasePayMe.Domain/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.AssemblyInfo.cs b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.AssemblyInfo.cs new file mode 100644 index 0000000..e77acde --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("PleasePayMe.Domain")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("PleasePayMe.Domain")] +[assembly: System.Reflection.AssemblyTitleAttribute("PleasePayMe.Domain")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.AssemblyInfoInputs.cache b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.AssemblyInfoInputs.cache new file mode 100644 index 0000000..d609d47 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +ab6d39baad61236ecbdcc378f272e961ebf56c7a8eb062f59b2cfdf14e280522 diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.GeneratedMSBuildEditorConfig.editorconfig b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..64d5362 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,15 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = PleasePayMe.Domain +build_property.ProjectDir = C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.GlobalUsings.g.cs b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.assets.cache b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.assets.cache new file mode 100644 index 0000000..e227d8e Binary files /dev/null and b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.assets.cache differ diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.csproj.CoreCompileInputs.cache b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..097010a --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +c959e71bd9cf7b314582ba7a7da8d75f81ea2b5e61cdf82de8ab5fbdb5eb0e47 diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.csproj.FileListAbsolute.txt b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..36c4356 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.csproj.FileListAbsolute.txt @@ -0,0 +1,11 @@ +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\bin\Release\net9.0\PleasePayMe.Domain.deps.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\bin\Release\net9.0\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\bin\Release\net9.0\PleasePayMe.Domain.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Release\net9.0\PleasePayMe.Domain.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Release\net9.0\PleasePayMe.Domain.AssemblyInfoInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Release\net9.0\PleasePayMe.Domain.AssemblyInfo.cs +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Release\net9.0\PleasePayMe.Domain.csproj.CoreCompileInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Release\net9.0\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Release\net9.0\refint\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Release\net9.0\PleasePayMe.Domain.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Domain\obj\Release\net9.0\ref\PleasePayMe.Domain.dll diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..0c885d2 Binary files /dev/null and b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..db59d49 Binary files /dev/null and b/src/PleasePayMe.Domain/obj/Release/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/ref/PleasePayMe.Domain.dll b/src/PleasePayMe.Domain/obj/Release/net9.0/ref/PleasePayMe.Domain.dll new file mode 100644 index 0000000..0dd0cb6 Binary files /dev/null and b/src/PleasePayMe.Domain/obj/Release/net9.0/ref/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Domain/obj/Release/net9.0/refint/PleasePayMe.Domain.dll b/src/PleasePayMe.Domain/obj/Release/net9.0/refint/PleasePayMe.Domain.dll new file mode 100644 index 0000000..0dd0cb6 Binary files /dev/null and b/src/PleasePayMe.Domain/obj/Release/net9.0/refint/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Domain/obj/project.assets.json b/src/PleasePayMe.Domain/obj/project.assets.json new file mode 100644 index 0000000..e80c035 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/project.assets.json @@ -0,0 +1,72 @@ +{ + "version": 3, + "targets": { + "net9.0": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + "net9.0": [] + }, + "packageFolders": { + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "projectName": "PleasePayMe.Domain", + "projectPath": "C:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "C:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Domain/obj/project.nuget.cache b/src/PleasePayMe.Domain/obj/project.nuget.cache new file mode 100644 index 0000000..6eb0b51 --- /dev/null +++ b/src/PleasePayMe.Domain/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "vScRhZTxWGs=", + "success": true, + "projectFilePath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file diff --git a/src/PleasePayMe.Infrastructure/Data/AppDbContext.cs b/src/PleasePayMe.Infrastructure/Data/AppDbContext.cs new file mode 100644 index 0000000..cc74795 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/AppDbContext.cs @@ -0,0 +1,122 @@ +using Microsoft.EntityFrameworkCore; +using PleasePayMe.Domain.Entities; + +namespace PleasePayMe.Infrastructure.Data; + +public sealed class AppDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Users => Set(); + public DbSet Budgets => Set(); + public DbSet Expenses => Set(); + public DbSet Jobs => Set(); + public DbSet TelegramYandexLinks => Set(); + public DbSet TelegramLinkChallenges => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(e => + { + e.ToTable("users"); + e.HasKey(x => x.UserId); + e.Property(x => x.UserId).HasColumnName("user_id").ValueGeneratedNever(); + e.Property(x => x.SelectedBudgetId).HasColumnName("selected_budget_id"); + e.Property(x => x.CreatedAt).HasColumnName("created_at"); + e.HasOne(x => x.SelectedBudget) + .WithMany() + .HasForeignKey(x => x.SelectedBudgetId) + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity(e => + { + e.ToTable("budgets"); + e.HasKey(x => x.Id); + e.Property(x => x.Id).HasColumnName("id"); + e.Property(x => x.UserId).HasColumnName("user_id"); + e.Property(x => x.Name).HasColumnName("name").HasMaxLength(128); + e.Property(x => x.TotalAmount).HasColumnName("total_amount").HasPrecision(18, 2); + e.Property(x => x.StartDate).HasColumnName("start_date"); + e.Property(x => x.EndDate).HasColumnName("end_date"); + e.Property(x => x.Currency).HasColumnName("currency").HasMaxLength(8); + e.Property(x => x.IsActive).HasColumnName("is_active"); + e.Property(x => x.CreatedAt).HasColumnName("created_at"); + e.HasIndex(x => new { x.UserId, x.IsActive, x.Id }); + e.HasOne(x => x.User) + .WithMany(x => x.Budgets) + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity(e => + { + e.ToTable("expenses"); + e.HasKey(x => x.Id); + e.Property(x => x.Id).HasColumnName("id"); + e.Property(x => x.UserId).HasColumnName("user_id"); + e.Property(x => x.BudgetId).HasColumnName("budget_id"); + e.Property(x => x.Amount).HasColumnName("amount").HasPrecision(18, 2); + e.Property(x => x.Note).HasColumnName("note").HasMaxLength(512); + e.Property(x => x.SpentAt).HasColumnName("spent_at"); + e.Property(x => x.CreatedAt).HasColumnName("created_at"); + e.HasIndex(x => new { x.BudgetId, x.SpentAt }); + e.HasIndex(x => new { x.UserId, x.SpentAt }); + e.HasOne(x => x.User) + .WithMany(x => x.Expenses) + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Cascade); + e.HasOne(x => x.Budget) + .WithMany(x => x.Expenses) + .HasForeignKey(x => x.BudgetId) + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity(e => + { + e.ToTable("jobs"); + e.HasKey(x => x.Id); + e.Property(x => x.Id).HasColumnName("id"); + e.Property(x => x.UserId).HasColumnName("user_id"); + e.Property(x => x.Name).HasColumnName("name").HasMaxLength(128); + e.Property(x => x.SalaryAmount).HasColumnName("salary_amount").HasPrecision(18, 2); + e.Property(x => x.Currency).HasColumnName("currency").HasMaxLength(8); + e.Property(x => x.PayDays) + .HasColumnName("pay_days") + .HasColumnType("integer[]"); + e.Property(x => x.FirstPayPercent) + .HasColumnName("first_pay_percent") + .HasPrecision(5, 2); + e.Property(x => x.WeekendPolicy) + .HasColumnName("weekend_policy") + .HasConversion(); + e.Property(x => x.IsActive).HasColumnName("is_active"); + e.Property(x => x.CreatedAt).HasColumnName("created_at"); + e.HasIndex(x => new { x.UserId, x.IsActive, x.Id }); + e.HasOne(x => x.User) + .WithMany(x => x.Jobs) + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity(e => + { + e.ToTable("telegram_yandex_links"); + e.HasKey(x => x.TelegramUserId); + e.Property(x => x.TelegramUserId).HasColumnName("telegram_user_id").ValueGeneratedNever(); + e.Property(x => x.YandexUserId).HasColumnName("yandex_user_id"); + e.Property(x => x.LinkedAt).HasColumnName("linked_at"); + e.HasIndex(x => x.YandexUserId).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.ToTable("telegram_link_challenges"); + e.HasKey(x => x.Token); + e.Property(x => x.Token).HasColumnName("token").HasMaxLength(64); + e.Property(x => x.TelegramUserId).HasColumnName("telegram_user_id"); + e.Property(x => x.ExpiresAt).HasColumnName("expires_at"); + e.Property(x => x.CreatedAt).HasColumnName("created_at"); + e.Property(x => x.ConsumedAt).HasColumnName("consumed_at"); + e.HasIndex(x => new { x.TelegramUserId, x.ConsumedAt, x.ExpiresAt }); + }); + } +} diff --git a/src/PleasePayMe.Infrastructure/Data/AppDbContextFactory.cs b/src/PleasePayMe.Infrastructure/Data/AppDbContextFactory.cs new file mode 100644 index 0000000..45c6e0f --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/AppDbContextFactory.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace PleasePayMe.Infrastructure.Data; + +public sealed class AppDbContextFactory : IDesignTimeDbContextFactory +{ + public AppDbContext CreateDbContext(string[] args) + { + var connectionString = + Environment.GetEnvironmentVariable("ConnectionStrings__Default") + ?? "Host=localhost;Port=5432;Database=please_pay_me;Username=ppm;Password=ppm"; + + var options = new DbContextOptionsBuilder() + .UseNpgsql(connectionString) + .Options; + return new AppDbContext(options); + } +} diff --git a/src/PleasePayMe.Infrastructure/Data/Migrations/20260913073847_InitialCreate.Designer.cs b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913073847_InitialCreate.Designer.cs new file mode 100644 index 0000000..f104610 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913073847_InitialCreate.Designer.cs @@ -0,0 +1,200 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using PleasePayMe.Infrastructure.Data; + +#nullable disable + +namespace PleasePayMe.Infrastructure.Data.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260913073847_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasColumnName("currency"); + + b.Property("EndDate") + .HasColumnType("date") + .HasColumnName("end_date"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.Property("StartDate") + .HasColumnType("date") + .HasColumnName("start_date"); + + b.Property("TotalAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("total_amount"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "IsActive", "Id"); + + b.ToTable("budgets", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Expense", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("amount"); + + b.Property("BudgetId") + .HasColumnType("bigint") + .HasColumnName("budget_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Note") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("note"); + + b.Property("SpentAt") + .HasColumnType("date") + .HasColumnName("spent_at"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("BudgetId", "SpentAt"); + + b.HasIndex("UserId", "SpentAt"); + + b.ToTable("expenses", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("SelectedBudgetId") + .HasColumnType("bigint") + .HasColumnName("selected_budget_id"); + + b.HasKey("UserId"); + + b.HasIndex("SelectedBudgetId"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Budgets") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Expense", b => + { + b.HasOne("PleasePayMe.Domain.Entities.Budget", "Budget") + .WithMany("Expenses") + .HasForeignKey("BudgetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Expenses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Budget"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.HasOne("PleasePayMe.Domain.Entities.Budget", "SelectedBudget") + .WithMany() + .HasForeignKey("SelectedBudgetId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("SelectedBudget"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.Navigation("Expenses"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.Navigation("Budgets"); + + b.Navigation("Expenses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/PleasePayMe.Infrastructure/Data/Migrations/20260913073847_InitialCreate.cs b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913073847_InitialCreate.cs new file mode 100644 index 0000000..94ffdcd --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913073847_InitialCreate.cs @@ -0,0 +1,130 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace PleasePayMe.Infrastructure.Data.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "budgets", + columns: table => new + { + id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(type: "bigint", nullable: false), + name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + total_amount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + start_date = table.Column(type: "date", nullable: false), + end_date = table.Column(type: "date", nullable: false), + currency = table.Column(type: "character varying(8)", maxLength: 8, nullable: false), + is_active = table.Column(type: "boolean", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_budgets", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "users", + columns: table => new + { + user_id = table.Column(type: "bigint", nullable: false), + selected_budget_id = table.Column(type: "bigint", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_users", x => x.user_id); + table.ForeignKey( + name: "FK_users_budgets_selected_budget_id", + column: x => x.selected_budget_id, + principalTable: "budgets", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "expenses", + columns: table => new + { + id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(type: "bigint", nullable: false), + budget_id = table.Column(type: "bigint", nullable: false), + amount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + note = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + spent_at = table.Column(type: "date", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_expenses", x => x.id); + table.ForeignKey( + name: "FK_expenses_budgets_budget_id", + column: x => x.budget_id, + principalTable: "budgets", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_expenses_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_budgets_user_id_is_active_id", + table: "budgets", + columns: new[] { "user_id", "is_active", "id" }); + + migrationBuilder.CreateIndex( + name: "IX_expenses_budget_id_spent_at", + table: "expenses", + columns: new[] { "budget_id", "spent_at" }); + + migrationBuilder.CreateIndex( + name: "IX_expenses_user_id_spent_at", + table: "expenses", + columns: new[] { "user_id", "spent_at" }); + + migrationBuilder.CreateIndex( + name: "IX_users_selected_budget_id", + table: "users", + column: "selected_budget_id"); + + migrationBuilder.AddForeignKey( + name: "FK_budgets_users_user_id", + table: "budgets", + column: "user_id", + principalTable: "users", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_budgets_users_user_id", + table: "budgets"); + + migrationBuilder.DropTable( + name: "expenses"); + + migrationBuilder.DropTable( + name: "users"); + + migrationBuilder.DropTable( + name: "budgets"); + } + } +} diff --git a/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075150_AddJobs.Designer.cs b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075150_AddJobs.Designer.cs new file mode 100644 index 0000000..356e0c1 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075150_AddJobs.Designer.cs @@ -0,0 +1,264 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using PleasePayMe.Infrastructure.Data; + +#nullable disable + +namespace PleasePayMe.Infrastructure.Data.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260913075150_AddJobs")] + partial class AddJobs + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasColumnName("currency"); + + b.Property("EndDate") + .HasColumnType("date") + .HasColumnName("end_date"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.Property("StartDate") + .HasColumnType("date") + .HasColumnName("start_date"); + + b.Property("TotalAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("total_amount"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "IsActive", "Id"); + + b.ToTable("budgets", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Expense", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("amount"); + + b.Property("BudgetId") + .HasColumnType("bigint") + .HasColumnName("budget_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Note") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("note"); + + b.Property("SpentAt") + .HasColumnType("date") + .HasColumnName("spent_at"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("BudgetId", "SpentAt"); + + b.HasIndex("UserId", "SpentAt"); + + b.ToTable("expenses", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasColumnName("currency"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.PrimitiveCollection>("PayDays") + .IsRequired() + .HasColumnType("integer[]") + .HasColumnName("pay_days"); + + b.Property("SalaryAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("salary_amount"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "IsActive", "Id"); + + b.ToTable("jobs", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("SelectedBudgetId") + .HasColumnType("bigint") + .HasColumnName("selected_budget_id"); + + b.HasKey("UserId"); + + b.HasIndex("SelectedBudgetId"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Budgets") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Expense", b => + { + b.HasOne("PleasePayMe.Domain.Entities.Budget", "Budget") + .WithMany("Expenses") + .HasForeignKey("BudgetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Expenses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Budget"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Job", b => + { + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Jobs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.HasOne("PleasePayMe.Domain.Entities.Budget", "SelectedBudget") + .WithMany() + .HasForeignKey("SelectedBudgetId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("SelectedBudget"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.Navigation("Expenses"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.Navigation("Budgets"); + + b.Navigation("Expenses"); + + b.Navigation("Jobs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075150_AddJobs.cs b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075150_AddJobs.cs new file mode 100644 index 0000000..ea9d47d --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075150_AddJobs.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace PleasePayMe.Infrastructure.Data.Migrations +{ + /// + public partial class AddJobs : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "jobs", + columns: table => new + { + id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + user_id = table.Column(type: "bigint", nullable: false), + name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + salary_amount = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false), + currency = table.Column(type: "character varying(8)", maxLength: 8, nullable: false), + pay_days = table.Column>(type: "integer[]", nullable: false), + is_active = table.Column(type: "boolean", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_jobs", x => x.id); + table.ForeignKey( + name: "FK_jobs_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_jobs_user_id_is_active_id", + table: "jobs", + columns: new[] { "user_id", "is_active", "id" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "jobs"); + } + } +} diff --git a/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075924_JobPaySplitAndWeekend.Designer.cs b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075924_JobPaySplitAndWeekend.Designer.cs new file mode 100644 index 0000000..49c1ef1 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075924_JobPaySplitAndWeekend.Designer.cs @@ -0,0 +1,273 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using PleasePayMe.Infrastructure.Data; + +#nullable disable + +namespace PleasePayMe.Infrastructure.Data.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260913075924_JobPaySplitAndWeekend")] + partial class JobPaySplitAndWeekend + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasColumnName("currency"); + + b.Property("EndDate") + .HasColumnType("date") + .HasColumnName("end_date"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.Property("StartDate") + .HasColumnType("date") + .HasColumnName("start_date"); + + b.Property("TotalAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("total_amount"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "IsActive", "Id"); + + b.ToTable("budgets", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Expense", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("amount"); + + b.Property("BudgetId") + .HasColumnType("bigint") + .HasColumnName("budget_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Note") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("note"); + + b.Property("SpentAt") + .HasColumnType("date") + .HasColumnName("spent_at"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("BudgetId", "SpentAt"); + + b.HasIndex("UserId", "SpentAt"); + + b.ToTable("expenses", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasColumnName("currency"); + + b.Property("FirstPayPercent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)") + .HasColumnName("first_pay_percent"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.PrimitiveCollection>("PayDays") + .IsRequired() + .HasColumnType("integer[]") + .HasColumnName("pay_days"); + + b.Property("SalaryAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("salary_amount"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.Property("WeekendPolicy") + .HasColumnType("integer") + .HasColumnName("weekend_policy"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "IsActive", "Id"); + + b.ToTable("jobs", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("SelectedBudgetId") + .HasColumnType("bigint") + .HasColumnName("selected_budget_id"); + + b.HasKey("UserId"); + + b.HasIndex("SelectedBudgetId"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Budgets") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Expense", b => + { + b.HasOne("PleasePayMe.Domain.Entities.Budget", "Budget") + .WithMany("Expenses") + .HasForeignKey("BudgetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Expenses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Budget"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Job", b => + { + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Jobs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.HasOne("PleasePayMe.Domain.Entities.Budget", "SelectedBudget") + .WithMany() + .HasForeignKey("SelectedBudgetId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("SelectedBudget"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.Navigation("Expenses"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.Navigation("Budgets"); + + b.Navigation("Expenses"); + + b.Navigation("Jobs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075924_JobPaySplitAndWeekend.cs b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075924_JobPaySplitAndWeekend.cs new file mode 100644 index 0000000..a635a3d --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/Migrations/20260913075924_JobPaySplitAndWeekend.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PleasePayMe.Infrastructure.Data.Migrations +{ + /// + public partial class JobPaySplitAndWeekend : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "first_pay_percent", + table: "jobs", + type: "numeric(5,2)", + precision: 5, + scale: 2, + nullable: false, + defaultValue: 0m); + + migrationBuilder.AddColumn( + name: "weekend_policy", + table: "jobs", + type: "integer", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "first_pay_percent", + table: "jobs"); + + migrationBuilder.DropColumn( + name: "weekend_policy", + table: "jobs"); + } + } +} diff --git a/src/PleasePayMe.Infrastructure/Data/Migrations/20260920015312_TelegramYandexLink.Designer.cs b/src/PleasePayMe.Infrastructure/Data/Migrations/20260920015312_TelegramYandexLink.Designer.cs new file mode 100644 index 0000000..97e69e8 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/Migrations/20260920015312_TelegramYandexLink.Designer.cs @@ -0,0 +1,325 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using PleasePayMe.Infrastructure.Data; + +#nullable disable + +namespace PleasePayMe.Infrastructure.Data.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260920015312_TelegramYandexLink")] + partial class TelegramYandexLink + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasColumnName("currency"); + + b.Property("EndDate") + .HasColumnType("date") + .HasColumnName("end_date"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.Property("StartDate") + .HasColumnType("date") + .HasColumnName("start_date"); + + b.Property("TotalAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("total_amount"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "IsActive", "Id"); + + b.ToTable("budgets", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Expense", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("amount"); + + b.Property("BudgetId") + .HasColumnType("bigint") + .HasColumnName("budget_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Note") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("note"); + + b.Property("SpentAt") + .HasColumnType("date") + .HasColumnName("spent_at"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("BudgetId", "SpentAt"); + + b.HasIndex("UserId", "SpentAt"); + + b.ToTable("expenses", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasColumnName("currency"); + + b.Property("FirstPayPercent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)") + .HasColumnName("first_pay_percent"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.PrimitiveCollection>("PayDays") + .IsRequired() + .HasColumnType("integer[]") + .HasColumnName("pay_days"); + + b.Property("SalaryAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("salary_amount"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.Property("WeekendPolicy") + .HasColumnType("integer") + .HasColumnName("weekend_policy"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "IsActive", "Id"); + + b.ToTable("jobs", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.TelegramLinkChallenge", b => + { + b.Property("Token") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("token"); + + b.Property("ConsumedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("consumed_at"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("TelegramUserId") + .HasColumnType("bigint") + .HasColumnName("telegram_user_id"); + + b.HasKey("Token"); + + b.HasIndex("TelegramUserId", "ConsumedAt", "ExpiresAt"); + + b.ToTable("telegram_link_challenges", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.TelegramYandexLink", b => + { + b.Property("TelegramUserId") + .HasColumnType("bigint") + .HasColumnName("telegram_user_id"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("linked_at"); + + b.Property("YandexUserId") + .HasColumnType("bigint") + .HasColumnName("yandex_user_id"); + + b.HasKey("TelegramUserId"); + + b.HasIndex("YandexUserId") + .IsUnique(); + + b.ToTable("telegram_yandex_links", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("SelectedBudgetId") + .HasColumnType("bigint") + .HasColumnName("selected_budget_id"); + + b.HasKey("UserId"); + + b.HasIndex("SelectedBudgetId"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Budgets") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Expense", b => + { + b.HasOne("PleasePayMe.Domain.Entities.Budget", "Budget") + .WithMany("Expenses") + .HasForeignKey("BudgetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Expenses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Budget"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Job", b => + { + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Jobs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.HasOne("PleasePayMe.Domain.Entities.Budget", "SelectedBudget") + .WithMany() + .HasForeignKey("SelectedBudgetId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("SelectedBudget"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.Navigation("Expenses"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.Navigation("Budgets"); + + b.Navigation("Expenses"); + + b.Navigation("Jobs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/PleasePayMe.Infrastructure/Data/Migrations/20260920015312_TelegramYandexLink.cs b/src/PleasePayMe.Infrastructure/Data/Migrations/20260920015312_TelegramYandexLink.cs new file mode 100644 index 0000000..2f50090 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/Migrations/20260920015312_TelegramYandexLink.cs @@ -0,0 +1,64 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PleasePayMe.Infrastructure.Data.Migrations +{ + /// + public partial class TelegramYandexLink : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "telegram_link_challenges", + columns: table => new + { + token = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + telegram_user_id = table.Column(type: "bigint", nullable: false), + expires_at = table.Column(type: "timestamp with time zone", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + consumed_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_telegram_link_challenges", x => x.token); + }); + + migrationBuilder.CreateTable( + name: "telegram_yandex_links", + columns: table => new + { + telegram_user_id = table.Column(type: "bigint", nullable: false), + yandex_user_id = table.Column(type: "bigint", nullable: false), + linked_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_telegram_yandex_links", x => x.telegram_user_id); + }); + + migrationBuilder.CreateIndex( + name: "IX_telegram_link_challenges_telegram_user_id_consumed_at_expir~", + table: "telegram_link_challenges", + columns: new[] { "telegram_user_id", "consumed_at", "expires_at" }); + + migrationBuilder.CreateIndex( + name: "IX_telegram_yandex_links_yandex_user_id", + table: "telegram_yandex_links", + column: "yandex_user_id", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "telegram_link_challenges"); + + migrationBuilder.DropTable( + name: "telegram_yandex_links"); + } + } +} diff --git a/src/PleasePayMe.Infrastructure/Data/Migrations/AppDbContextModelSnapshot.cs b/src/PleasePayMe.Infrastructure/Data/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..568a9ec --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Data/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,322 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using PleasePayMe.Infrastructure.Data; + +#nullable disable + +namespace PleasePayMe.Infrastructure.Data.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasColumnName("currency"); + + b.Property("EndDate") + .HasColumnType("date") + .HasColumnName("end_date"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.Property("StartDate") + .HasColumnType("date") + .HasColumnName("start_date"); + + b.Property("TotalAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("total_amount"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "IsActive", "Id"); + + b.ToTable("budgets", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Expense", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("amount"); + + b.Property("BudgetId") + .HasColumnType("bigint") + .HasColumnName("budget_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Note") + .HasMaxLength(512) + .HasColumnType("character varying(512)") + .HasColumnName("note"); + + b.Property("SpentAt") + .HasColumnType("date") + .HasColumnName("spent_at"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("BudgetId", "SpentAt"); + + b.HasIndex("UserId", "SpentAt"); + + b.ToTable("expenses", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasColumnName("currency"); + + b.Property("FirstPayPercent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)") + .HasColumnName("first_pay_percent"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("name"); + + b.PrimitiveCollection>("PayDays") + .IsRequired() + .HasColumnType("integer[]") + .HasColumnName("pay_days"); + + b.Property("SalaryAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)") + .HasColumnName("salary_amount"); + + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.Property("WeekendPolicy") + .HasColumnType("integer") + .HasColumnName("weekend_policy"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "IsActive", "Id"); + + b.ToTable("jobs", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.TelegramLinkChallenge", b => + { + b.Property("Token") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("token"); + + b.Property("ConsumedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("consumed_at"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("TelegramUserId") + .HasColumnType("bigint") + .HasColumnName("telegram_user_id"); + + b.HasKey("Token"); + + b.HasIndex("TelegramUserId", "ConsumedAt", "ExpiresAt"); + + b.ToTable("telegram_link_challenges", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.TelegramYandexLink", b => + { + b.Property("TelegramUserId") + .HasColumnType("bigint") + .HasColumnName("telegram_user_id"); + + b.Property("LinkedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("linked_at"); + + b.Property("YandexUserId") + .HasColumnType("bigint") + .HasColumnName("yandex_user_id"); + + b.HasKey("TelegramUserId"); + + b.HasIndex("YandexUserId") + .IsUnique(); + + b.ToTable("telegram_yandex_links", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.Property("UserId") + .HasColumnType("bigint") + .HasColumnName("user_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("SelectedBudgetId") + .HasColumnType("bigint") + .HasColumnName("selected_budget_id"); + + b.HasKey("UserId"); + + b.HasIndex("SelectedBudgetId"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Budgets") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Expense", b => + { + b.HasOne("PleasePayMe.Domain.Entities.Budget", "Budget") + .WithMany("Expenses") + .HasForeignKey("BudgetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Expenses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Budget"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Job", b => + { + b.HasOne("PleasePayMe.Domain.Entities.User", "User") + .WithMany("Jobs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.HasOne("PleasePayMe.Domain.Entities.Budget", "SelectedBudget") + .WithMany() + .HasForeignKey("SelectedBudgetId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("SelectedBudget"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.Budget", b => + { + b.Navigation("Expenses"); + }); + + modelBuilder.Entity("PleasePayMe.Domain.Entities.User", b => + { + b.Navigation("Budgets"); + + b.Navigation("Expenses"); + + b.Navigation("Jobs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/PleasePayMe.Infrastructure/DependencyInjection.cs b/src/PleasePayMe.Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..eb54a4d --- /dev/null +++ b/src/PleasePayMe.Infrastructure/DependencyInjection.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using PleasePayMe.Application.Abstractions; +using PleasePayMe.Infrastructure.Data; +using PleasePayMe.Infrastructure.Services; + +namespace PleasePayMe.Infrastructure; + +public static class DependencyInjection +{ + public static IServiceCollection AddInfrastructure( + this IServiceCollection services, + IConfiguration configuration) + { + var connectionString = configuration.GetConnectionString("Default") + ?? configuration["DATABASE_URL"] + ?? throw new InvalidOperationException("Connection string 'Default' is required"); + + services.AddDbContext(options => + options.UseNpgsql(connectionString)); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + return services; + } +} diff --git a/src/PleasePayMe.Infrastructure/PleasePayMe.Infrastructure.csproj b/src/PleasePayMe.Infrastructure/PleasePayMe.Infrastructure.csproj new file mode 100644 index 0000000..f5b08b7 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/PleasePayMe.Infrastructure.csproj @@ -0,0 +1,22 @@ + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + net9.0 + enable + enable + + + diff --git a/src/PleasePayMe.Infrastructure/Services/BudgetService.cs b/src/PleasePayMe.Infrastructure/Services/BudgetService.cs new file mode 100644 index 0000000..8584398 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Services/BudgetService.cs @@ -0,0 +1,588 @@ +using Microsoft.EntityFrameworkCore; +using PleasePayMe.Application.Abstractions; +using PleasePayMe.Application.Contracts; +using PleasePayMe.Domain; +using PleasePayMe.Domain.Entities; +using PleasePayMe.Infrastructure.Data; + +namespace PleasePayMe.Infrastructure.Services; + +public sealed class BudgetService(AppDbContext db) : IBudgetService +{ + private const int DefaultPageSize = 8; + + public async Task> ListUserStatusesAsync(long userId, CancellationToken ct) + { + var budgets = await db.Budgets.AsNoTracking() + .Where(b => b.UserId == userId) + .OrderByDescending(b => b.IsActive) + .ThenByDescending(b => b.Id) + .ToListAsync(ct); + var selectedId = await GetSelectedBudgetIdAsync(userId, ct); + var result = new List(budgets.Count); + foreach (var budget in budgets) + { + result.Add(await BuildStatusAsync(budget, selectedId == budget.Id, ct)); + } + + return result; + } + + public async Task> ListAllStatusesAsync(CancellationToken ct) + { + var budgets = await db.Budgets.AsNoTracking() + .OrderBy(b => b.UserId) + .ThenByDescending(b => b.IsActive) + .ThenByDescending(b => b.Id) + .ToListAsync(ct); + var result = new List(budgets.Count); + foreach (var budget in budgets) + { + result.Add(await BuildStatusAsync(budget, selected: false, ct)); + } + + return result; + } + + public async Task GetStatusAsync(long userId, long? budgetId, CancellationToken ct) + { + var budget = await ResolveBudgetAsync(userId, budgetId, requireActive: false, ct) + ?? throw new DomainException("Сначала задай бюджет: /budget"); + var selectedId = await GetSelectedBudgetIdAsync(userId, ct); + return await BuildStatusAsync(budget, selectedId == budget.Id, ct); + } + + public async Task CreateBudgetAsync( + long userId, + decimal totalAmount, + DateOnly endDate, + string name, + bool isActive, + bool select, + DateOnly? startDate, + CancellationToken ct) + { + var start = startDate ?? DateOnly.FromDateTime(DateTime.UtcNow); + if (endDate < start) + { + throw new DomainException("Дата окончания не может быть раньше даты начала"); + } + + if (totalAmount <= 0) + { + throw new DomainException("Сумма бюджета должна быть больше нуля"); + } + + await EnsureUserAsync(userId, ct); + var budget = new Budget + { + UserId = userId, + Name = string.IsNullOrWhiteSpace(name) ? "Бюджет" : name.Trim(), + TotalAmount = totalAmount, + StartDate = start, + EndDate = endDate, + IsActive = isActive, + }; + db.Budgets.Add(budget); + await db.SaveChangesAsync(ct); + + if (select) + { + await SetSelectedAsync(userId, budget.Id, ct); + } + + return await BuildStatusAsync(budget, select, ct); + } + + public async Task UpdateBudgetAsync( + long userId, + long budgetId, + string? name, + decimal? totalAmount, + DateOnly? endDate, + DateOnly? startDate, + bool resetExpenses, + CancellationToken ct) + { + var budget = await db.Budgets.FirstOrDefaultAsync(b => b.Id == budgetId && b.UserId == userId, ct) + ?? throw new DomainException("Бюджет не найден"); + + var nextStart = startDate ?? budget.StartDate; + var nextEnd = endDate ?? budget.EndDate; + if (nextEnd < nextStart) + { + throw new DomainException("Дата окончания не может быть раньше даты начала"); + } + + if (totalAmount is <= 0) + { + throw new DomainException("Сумма бюджета должна быть больше нуля"); + } + + if (resetExpenses) + { + await db.Expenses.Where(e => e.BudgetId == budgetId).ExecuteDeleteAsync(ct); + } + + if (name is not null) + { + budget.Name = string.IsNullOrWhiteSpace(name) ? "Бюджет" : name.Trim(); + } + + if (totalAmount is not null) + { + budget.TotalAmount = totalAmount.Value; + } + + if (startDate is not null) + { + budget.StartDate = startDate.Value; + } + + if (endDate is not null) + { + budget.EndDate = endDate.Value; + } + + await db.SaveChangesAsync(ct); + var selectedId = await GetSelectedBudgetIdAsync(userId, ct); + return await BuildStatusAsync(budget, selectedId == budget.Id, ct); + } + + public async Task SetBudgetActiveAsync( + long userId, + long budgetId, + bool isActive, + CancellationToken ct) + { + var budget = await db.Budgets.FirstOrDefaultAsync(b => b.Id == budgetId && b.UserId == userId, ct) + ?? throw new DomainException("Бюджет не найден"); + budget.IsActive = isActive; + await db.SaveChangesAsync(ct); + + var selectedId = await GetSelectedBudgetIdAsync(userId, ct); + if (!isActive && selectedId == budgetId) + { + var next = await db.Budgets.AsNoTracking() + .Where(b => b.UserId == userId && b.IsActive) + .OrderByDescending(b => b.Id) + .Select(b => (long?)b.Id) + .FirstOrDefaultAsync(ct); + await SetSelectedAsync(userId, next, ct); + selectedId = next; + } + + return await BuildStatusAsync(budget, selectedId == budget.Id, ct); + } + + public async Task DeleteBudgetAsync(long userId, long budgetId, CancellationToken ct) + { + var budget = await db.Budgets.FirstOrDefaultAsync(b => b.Id == budgetId && b.UserId == userId, ct) + ?? throw new DomainException("Бюджет не найден"); + + var selectedId = await GetSelectedBudgetIdAsync(userId, ct); + var wasSelected = selectedId == budgetId; + + // Clear selection first to avoid FK issues if SetNull isn't applied yet. + if (wasSelected) + { + await SetSelectedAsync(userId, null, ct); + } + + db.Budgets.Remove(budget); + await db.SaveChangesAsync(ct); + + if (wasSelected) + { + var next = await db.Budgets.AsNoTracking() + .Where(b => b.UserId == userId) + .OrderByDescending(b => b.IsActive) + .ThenByDescending(b => b.Id) + .Select(b => (long?)b.Id) + .FirstOrDefaultAsync(ct); + if (next is not null) + { + await SetSelectedAsync(userId, next, ct); + } + } + } + + public async Task SelectBudgetAsync(long userId, long budgetId, CancellationToken ct) + { + var budget = await db.Budgets.AsNoTracking() + .FirstOrDefaultAsync(b => b.Id == budgetId && b.UserId == userId, ct) + ?? throw new DomainException("Бюджет не найден"); + await SetSelectedAsync(userId, budgetId, ct); + return await BuildStatusAsync(budget, selected: true, ct); + } + + public async Task UpsertBudgetAsync( + long userId, + decimal totalAmount, + DateOnly endDate, + bool resetExpenses, + string? name, + long? budgetId, + DateOnly? startDate, + CancellationToken ct) + { + if (budgetId is not null) + { + return await UpdateBudgetAsync( + userId, + budgetId.Value, + name, + totalAmount, + endDate, + startDate, + resetExpenses, + ct); + } + + var current = await ResolveBudgetAsync(userId, null, requireActive: false, ct); + if (current is null) + { + return await CreateBudgetAsync( + userId, + totalAmount, + endDate, + name ?? "Бюджет", + isActive: true, + select: true, + startDate, + ct); + } + + return await UpdateBudgetAsync( + userId, + current.Id, + name, + totalAmount, + endDate, + startDate, + resetExpenses, + ct); + } + + public async Task AddExpenseAsync( + long userId, + decimal amount, + string? note, + DateOnly? spentAt, + long? budgetId, + CancellationToken ct) + { + if (amount <= 0) + { + throw new DomainException("Сумма траты должна быть больше нуля"); + } + + var budget = await ResolveBudgetAsync(userId, budgetId, requireActive: false, ct) + ?? throw new DomainException("Сначала задай бюджет: /budget"); + + var day = spentAt ?? DateOnly.FromDateTime(DateTime.UtcNow); + var today = DateOnly.FromDateTime(DateTime.UtcNow); + if (day > today) + { + throw new DomainException("Нельзя добавить трату на будущую дату"); + } + + // Активный бюджет — только в своём периоде. Неактивный — лог без жёсткой привязки к датам. + if (budget.IsActive && (day < budget.StartDate || day > budget.EndDate)) + { + throw new DomainException( + $"Дата вне периода бюджета ({budget.StartDate:dd.MM.yyyy}–{budget.EndDate:dd.MM.yyyy})"); + } + + await EnsureUserAsync(userId, ct); + db.Expenses.Add(new Expense + { + UserId = userId, + BudgetId = budget.Id, + Amount = amount, + Note = note, + SpentAt = day, + }); + await db.SaveChangesAsync(ct); + + var selectedId = await GetSelectedBudgetIdAsync(userId, ct); + return await BuildStatusAsync(budget, selectedId == budget.Id, ct); + } + + public async Task<(BudgetStatusDto Status, decimal DeletedAmount)?> UndoLastExpenseAsync( + long userId, + long? budgetId, + CancellationToken ct) + { + var budget = await ResolveBudgetAsync(userId, budgetId, requireActive: false, ct); + IQueryable query = db.Expenses.Where(e => e.UserId == userId); + if (budget is not null) + { + query = query.Where(e => e.BudgetId == budget.Id); + } + + var last = await query.OrderByDescending(e => e.Id).FirstOrDefaultAsync(ct); + if (last is null) + { + return null; + } + + var amount = last.Amount; + var lastBudgetId = last.BudgetId; + db.Expenses.Remove(last); + await db.SaveChangesAsync(ct); + var status = await GetStatusAsync(userId, lastBudgetId, ct); + return (status, amount); + } + + public async Task GetPeriodExpensesPageAsync( + long userId, + int page, + int pageSize, + long? budgetId, + CancellationToken ct) + { + var budget = await ResolveBudgetAsync(userId, budgetId, requireActive: false, ct) + ?? throw new DomainException("Сначала задай бюджет: /budget"); + return await PageExpensesAsync( + budget, + db.Expenses.AsNoTracking().Where(e => e.BudgetId == budget.Id), + page, + pageSize, + ct); + } + + public async Task GetExpensesOnDatePageAsync( + long userId, + DateOnly day, + int page, + int pageSize, + long? budgetId, + CancellationToken ct) + { + var budget = await ResolveBudgetAsync(userId, budgetId, requireActive: false, ct) + ?? throw new DomainException("Сначала задай бюджет: /budget"); + return await PageExpensesAsync( + budget, + db.Expenses.AsNoTracking().Where(e => e.BudgetId == budget.Id && e.SpentAt == day), + page, + pageSize, + ct); + } + + public async Task GetAllExpensesPageAsync( + long userId, + int page, + int pageSize, + DateOnly? spentAt, + CancellationToken ct) + { + IQueryable query = db.Expenses.AsNoTracking().Where(e => e.UserId == userId); + if (spentAt is not null) + { + query = query.Where(e => e.SpentAt == spentAt.Value); + } + + return await PageExpensesAsync(budget: null, query, page, pageSize, ct); + } + + public async Task GetExpensesInRangeAsync( + long userId, + DateOnly from, + DateOnly to, + long? budgetId, + CancellationToken ct) + { + if (to < from) + { + throw new DomainException("Дата «до» не может быть раньше даты «с»"); + } + + if (to.DayNumber - from.DayNumber > 93) + { + throw new DomainException("Диапазон не больше 93 дней"); + } + + var budgetQuery = db.Budgets.AsNoTracking().Where(b => b.UserId == userId); + if (budgetId is not null) + { + budgetQuery = budgetQuery.Where(b => b.Id == budgetId.Value); + } + + var budgetIds = await budgetQuery.Select(b => b.Id).ToListAsync(ct); + if (budgetIds.Count == 0) + { + return new ExpensesRangeDto(Array.Empty()); + } + + var items = await db.Expenses.AsNoTracking() + .Where(e => budgetIds.Contains(e.BudgetId) && e.SpentAt >= from && e.SpentAt <= to) + .OrderByDescending(e => e.SpentAt) + .ThenByDescending(e => e.Id) + .Take(2000) + .ToListAsync(ct); + + return new ExpensesRangeDto(items); + } + + private static async Task PageExpensesAsync( + Budget? budget, + IQueryable query, + int page, + int pageSize, + CancellationToken ct) + { + if (page < 0) + { + page = 0; + } + + if (pageSize < 1) + { + pageSize = DefaultPageSize; + } + + pageSize = Math.Clamp(pageSize, 1, 100); + var totalCount = await query.CountAsync(ct); + var totalSum = await query.SumAsync(e => (decimal?)e.Amount, ct) ?? 0m; + var totalPages = totalCount == 0 ? 1 : (int)Math.Ceiling(totalCount / (double)pageSize); + if (page >= totalPages) + { + page = totalPages - 1; + } + + var items = await query + .OrderByDescending(e => e.SpentAt) + .ThenByDescending(e => e.Id) + .Skip(page * pageSize) + .Take(pageSize) + .ToListAsync(ct); + + return new ExpensesPageDto(budget, page, totalPages, totalCount, totalSum, pageSize, items); + } + + private async Task BuildStatusAsync(Budget budget, bool selected, CancellationToken ct) + { + var today = DateOnly.FromDateTime(DateTime.UtcNow); + var totalSpent = await db.Expenses.AsNoTracking() + .Where(e => e.BudgetId == budget.Id) + .SumAsync(e => (decimal?)e.Amount, ct) ?? 0m; + var spentToday = await db.Expenses.AsNoTracking() + .Where(e => e.BudgetId == budget.Id && e.SpentAt == today) + .SumAsync(e => (decimal?)e.Amount, ct) ?? 0m; + var remaining = budget.TotalAmount - totalSpent; + + int daysLeft; + decimal dailyLimit; + decimal remainingToday; + bool isExpired; + if (today > budget.EndDate) + { + daysLeft = 0; + dailyLimit = 0; + remainingToday = 0; + isExpired = true; + } + else + { + daysLeft = budget.EndDate.DayNumber - today.DayNumber + 1; + var remainingAtDayStart = remaining + spentToday; + dailyLimit = daysLeft > 0 ? remainingAtDayStart / daysLeft : 0; + remainingToday = dailyLimit - spentToday; + isExpired = false; + } + + return new BudgetStatusDto( + budget, + today, + daysLeft, + totalSpent, + remaining, + Math.Max(dailyLimit, 0), + spentToday, + remainingToday, + spentToday > dailyLimit && daysLeft > 0, + remaining < 0, + isExpired, + selected); + } + + private async Task ResolveBudgetAsync( + long userId, + long? budgetId, + bool requireActive, + CancellationToken ct) + { + if (budgetId is not null) + { + var exact = await db.Budgets.AsNoTracking() + .FirstOrDefaultAsync(b => b.Id == budgetId && b.UserId == userId, ct); + if (exact is null) + { + return null; + } + + if (requireActive && !exact.IsActive) + { + throw new DomainException("Бюджет неактивен — включи его или выбери другой"); + } + + return exact; + } + + var selectedId = await GetSelectedBudgetIdAsync(userId, ct); + if (selectedId is not null) + { + var selected = await db.Budgets.AsNoTracking() + .FirstOrDefaultAsync(b => b.Id == selectedId && b.UserId == userId, ct); + if (selected is not null && (!requireActive || selected.IsActive)) + { + return selected; + } + } + + var fallback = await db.Budgets.AsNoTracking() + .Where(b => b.UserId == userId) + .OrderByDescending(b => b.IsActive) + .ThenByDescending(b => b.Id) + .FirstOrDefaultAsync(ct); + if (fallback is null) + { + return null; + } + + if (requireActive && !fallback.IsActive) + { + throw new DomainException("Нет активного бюджета — создай или включи существующий"); + } + + return fallback; + } + + private async Task EnsureUserAsync(long userId, CancellationToken ct) + { + if (await db.Users.AnyAsync(u => u.UserId == userId, ct)) + { + return; + } + + db.Users.Add(new User { UserId = userId }); + await db.SaveChangesAsync(ct); + } + + private async Task GetSelectedBudgetIdAsync(long userId, CancellationToken ct) + { + return await db.Users.AsNoTracking() + .Where(u => u.UserId == userId) + .Select(u => u.SelectedBudgetId) + .FirstOrDefaultAsync(ct); + } + + private async Task SetSelectedAsync(long userId, long? budgetId, CancellationToken ct) + { + await EnsureUserAsync(userId, ct); + var user = await db.Users.FirstAsync(u => u.UserId == userId, ct); + user.SelectedBudgetId = budgetId; + await db.SaveChangesAsync(ct); + } +} diff --git a/src/PleasePayMe.Infrastructure/Services/JobService.cs b/src/PleasePayMe.Infrastructure/Services/JobService.cs new file mode 100644 index 0000000..b421a50 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Services/JobService.cs @@ -0,0 +1,143 @@ +using Microsoft.EntityFrameworkCore; +using PleasePayMe.Application.Abstractions; +using PleasePayMe.Application.Contracts; +using PleasePayMe.Application.Jobs; +using PleasePayMe.Domain; +using PleasePayMe.Domain.Entities; +using PleasePayMe.Infrastructure.Data; + +namespace PleasePayMe.Infrastructure.Services; + +public sealed class JobService(AppDbContext db) : IJobService +{ + public async Task> ListAsync(long userId, CancellationToken ct) + { + var jobs = await db.Jobs.AsNoTracking() + .Where(j => j.UserId == userId) + .OrderByDescending(j => j.IsActive) + .ThenByDescending(j => j.Id) + .ToListAsync(ct); + return jobs.Select(ToDto).ToList(); + } + + public async Task GetAsync(long userId, long jobId, CancellationToken ct) + { + var job = await db.Jobs.AsNoTracking() + .FirstOrDefaultAsync(j => j.Id == jobId && j.UserId == userId, ct) + ?? throw new DomainException("Работа не найдена"); + return ToDto(job); + } + + public async Task CreateAsync( + long userId, + string name, + decimal salaryAmount, + IReadOnlyList payDays, + decimal firstPayPercent, + WeekendPayPolicy weekendPolicy, + bool isActive, + CancellationToken ct) + { + Validate(name, salaryAmount); + var days = PaySchedule.NormalizePayDays(payDays); + var percent = PaySchedule.NormalizeFirstPayPercent(days.Count, firstPayPercent); + await EnsureUserAsync(userId, ct); + + var job = new Job + { + UserId = userId, + Name = name.Trim(), + SalaryAmount = salaryAmount, + PayDays = days.ToList(), + FirstPayPercent = percent, + WeekendPolicy = weekendPolicy, + IsActive = isActive, + }; + db.Jobs.Add(job); + await db.SaveChangesAsync(ct); + return ToDto(job); + } + + public async Task UpdateAsync( + long userId, + long jobId, + string name, + decimal salaryAmount, + IReadOnlyList payDays, + decimal firstPayPercent, + WeekendPayPolicy weekendPolicy, + bool isActive, + CancellationToken ct) + { + Validate(name, salaryAmount); + var days = PaySchedule.NormalizePayDays(payDays); + var percent = PaySchedule.NormalizeFirstPayPercent(days.Count, firstPayPercent); + var job = await db.Jobs.FirstOrDefaultAsync(j => j.Id == jobId && j.UserId == userId, ct) + ?? throw new DomainException("Работа не найдена"); + + job.Name = name.Trim(); + job.SalaryAmount = salaryAmount; + job.PayDays = days.ToList(); + job.FirstPayPercent = percent; + job.WeekendPolicy = weekendPolicy; + job.IsActive = isActive; + await db.SaveChangesAsync(ct); + return ToDto(job); + } + + public async Task DeleteAsync(long userId, long jobId, CancellationToken ct) + { + var job = await db.Jobs.FirstOrDefaultAsync(j => j.Id == jobId && j.UserId == userId, ct) + ?? throw new DomainException("Работа не найдена"); + db.Jobs.Remove(job); + await db.SaveChangesAsync(ct); + } + + private static void Validate(string name, decimal salaryAmount) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new DomainException("Укажи название работы"); + } + + if (salaryAmount <= 0) + { + throw new DomainException("Зарплата должна быть больше нуля"); + } + } + + private static JobDto ToDto(Job job) + { + var today = DateOnly.FromDateTime(DateTime.UtcNow); + var days = job.PayDays.OrderBy(d => d).ToList(); + var percent = PaySchedule.NormalizeFirstPayPercent(days.Count, job.FirstPayPercent); + var next = PaySchedule.NextPays( + days, + job.SalaryAmount, + percent, + job.WeekendPolicy, + today); + return new JobDto( + job.Id, + job.UserId, + job.Name, + job.SalaryAmount, + job.Currency, + days, + percent, + job.WeekendPolicy, + job.IsActive, + next.Select(p => new UpcomingPayDto(p.Date, p.ScheduledDay, p.Percent, p.Amount)).ToList()); + } + + private async Task EnsureUserAsync(long userId, CancellationToken ct) + { + if (await db.Users.AnyAsync(u => u.UserId == userId, ct)) + { + return; + } + + db.Users.Add(new User { UserId = userId }); + await db.SaveChangesAsync(ct); + } +} diff --git a/src/PleasePayMe.Infrastructure/Services/TelegramLinkService.cs b/src/PleasePayMe.Infrastructure/Services/TelegramLinkService.cs new file mode 100644 index 0000000..ce094a2 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/Services/TelegramLinkService.cs @@ -0,0 +1,174 @@ +using System.Security.Cryptography; +using Microsoft.EntityFrameworkCore; +using PleasePayMe.Application.Abstractions; +using PleasePayMe.Domain; +using PleasePayMe.Domain.Entities; +using PleasePayMe.Infrastructure.Data; + +namespace PleasePayMe.Infrastructure.Services; + +public sealed class TelegramLinkService(AppDbContext db) : ITelegramLinkService +{ + private static readonly TimeSpan ChallengeTtl = TimeSpan.FromMinutes(30); + + public async Task FindYandexUserIdAsync(long telegramUserId, CancellationToken ct) + { + return await db.TelegramYandexLinks.AsNoTracking() + .Where(x => x.TelegramUserId == telegramUserId) + .Select(x => (long?)x.YandexUserId) + .FirstOrDefaultAsync(ct); + } + + public async Task CreateOrReuseChallengeTokenAsync(long telegramUserId, CancellationToken ct) + { + if (telegramUserId <= 0 || YandexIdentity.IsYandexUserId(telegramUserId)) + { + throw new DomainException("user_id must be a Telegram id"); + } + + var now = DateTimeOffset.UtcNow; + var existing = await db.TelegramLinkChallenges + .Where(x => + x.TelegramUserId == telegramUserId + && x.ConsumedAt == null + && x.ExpiresAt > now) + .OrderByDescending(x => x.ExpiresAt) + .FirstOrDefaultAsync(ct); + + if (existing is not null) + { + existing.ExpiresAt = now.Add(ChallengeTtl); + await db.SaveChangesAsync(ct); + return existing.Token; + } + + var challenge = new TelegramLinkChallenge + { + Token = Convert.ToHexString(RandomNumberGenerator.GetBytes(32)).ToLowerInvariant(), + TelegramUserId = telegramUserId, + CreatedAt = now, + ExpiresAt = now.Add(ChallengeTtl), + }; + db.TelegramLinkChallenges.Add(challenge); + await db.SaveChangesAsync(ct); + return challenge.Token; + } + + public async Task CompleteAsync(string token, long yandexUserId, CancellationToken ct) + { + if (!YandexIdentity.IsYandexUserId(yandexUserId)) + { + throw new DomainException("Войдите через Яндекс, чтобы привязать Telegram"); + } + + var normalized = (token ?? "").Trim().ToLowerInvariant(); + if (normalized.Length != 64) + { + throw new DomainException("Ссылка для входа недействительна. Откройте новую из Telegram-бота."); + } + + await using var tx = await db.Database.BeginTransactionAsync(ct); + + var challenge = await db.TelegramLinkChallenges + .FirstOrDefaultAsync(x => x.Token == normalized, ct); + if (challenge is null) + { + throw new DomainException("Ссылка для входа недействительна. Откройте новую из Telegram-бота."); + } + + var now = DateTimeOffset.UtcNow; + if (challenge.ConsumedAt is not null) + { + var already = await db.TelegramYandexLinks + .AsNoTracking() + .FirstOrDefaultAsync(x => x.TelegramUserId == challenge.TelegramUserId, ct); + if (already is not null && already.YandexUserId == yandexUserId) + { + await tx.CommitAsync(ct); + return; + } + + throw new DomainException("Ссылка уже использована. Откройте новую из Telegram-бота."); + } + + if (challenge.ExpiresAt <= now) + { + throw new DomainException("Ссылка для входа устарела. Откройте новую из Telegram-бота."); + } + + var telegramId = challenge.TelegramUserId; + var existingLink = await db.TelegramYandexLinks + .FirstOrDefaultAsync(x => x.TelegramUserId == telegramId, ct); + if (existingLink is not null) + { + if (existingLink.YandexUserId != yandexUserId) + { + throw new DomainException("Этот Telegram уже привязан к другому Яндекс-аккаунту"); + } + + challenge.ConsumedAt = now; + await db.SaveChangesAsync(ct); + await tx.CommitAsync(ct); + return; + } + + var yandexTaken = await db.TelegramYandexLinks + .AnyAsync(x => x.YandexUserId == yandexUserId, ct); + if (yandexTaken) + { + throw new DomainException("Этот Яндекс уже привязан к другому Telegram"); + } + + await ReassignTelegramDataAsync(telegramId, yandexUserId, ct); + + db.TelegramYandexLinks.Add(new TelegramYandexLink + { + TelegramUserId = telegramId, + YandexUserId = yandexUserId, + LinkedAt = now, + }); + challenge.ConsumedAt = now; + await db.SaveChangesAsync(ct); + await tx.CommitAsync(ct); + } + + private async Task ReassignTelegramDataAsync(long telegramId, long yandexUserId, CancellationToken ct) + { + if (!await db.Users.AnyAsync(u => u.UserId == yandexUserId, ct)) + { + db.Users.Add(new User { UserId = yandexUserId }); + await db.SaveChangesAsync(ct); + } + + var telegramUser = await db.Users.FirstOrDefaultAsync(u => u.UserId == telegramId, ct); + long? telegramSelected = telegramUser?.SelectedBudgetId; + if (telegramUser is not null) + { + telegramUser.SelectedBudgetId = null; + await db.SaveChangesAsync(ct); + } + + await db.Expenses + .Where(x => x.UserId == telegramId) + .ExecuteUpdateAsync(s => s.SetProperty(x => x.UserId, yandexUserId), ct); + await db.Jobs + .Where(x => x.UserId == telegramId) + .ExecuteUpdateAsync(s => s.SetProperty(x => x.UserId, yandexUserId), ct); + await db.Budgets + .Where(x => x.UserId == telegramId) + .ExecuteUpdateAsync(s => s.SetProperty(x => x.UserId, yandexUserId), ct); + + var yandexUser = await db.Users.FirstAsync(u => u.UserId == yandexUserId, ct); + if (yandexUser.SelectedBudgetId is null && telegramSelected is not null) + { + yandexUser.SelectedBudgetId = telegramSelected; + } + + if (telegramUser is not null) + { + db.Users.Remove(telegramUser); + } + + await db.SaveChangesAsync(ct); + } +} diff --git a/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Application.dll b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Application.dll new file mode 100644 index 0000000..bfec08b Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Application.pdb b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Application.pdb new file mode 100644 index 0000000..c531d97 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Application.pdb differ diff --git a/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..e1642b6 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..9c94240 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.deps.json b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.deps.json new file mode 100644 index 0000000..d46a01a --- /dev/null +++ b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.deps.json @@ -0,0 +1,930 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v9.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v9.0": { + "PleasePayMe.Infrastructure/1.0.0": { + "dependencies": { + "Microsoft.EntityFrameworkCore.Design": "9.0.4", + "Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.4", + "PleasePayMe.Application": "1.0.0", + "PleasePayMe.Domain": "1.0.0" + }, + "runtime": { + "PleasePayMe.Infrastructure.dll": {} + } + }, + "Humanizer.Core/2.14.1": { + "runtime": { + "lib/net6.0/Humanizer.dll": { + "assemblyVersion": "2.14.0.0", + "fileVersion": "2.14.1.48190" + } + } + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "runtime": { + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "Microsoft.Build.Framework/17.8.3": {}, + "Microsoft.Build.Locator/1.7.8": { + "runtime": { + "lib/net6.0/Microsoft.Build.Locator.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.7.8.28074" + } + } + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": {}, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.4", + "System.Collections.Immutable": "7.0.0", + "System.Reflection.Metadata": "7.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "dependencies": { + "Microsoft.CodeAnalysis.Common": "4.8.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.Common": "4.8.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Bcl.AsyncInterfaces": "7.0.0", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "System.Composition": "7.0.0", + "System.IO.Pipelines": "7.0.0", + "System.Threading.Channels": "7.0.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "dependencies": { + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.Common": "4.8.0", + "System.Text.Json": "9.0.4" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + }, + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "9.0.4", + "Microsoft.EntityFrameworkCore.Analyzers": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": {}, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.Build.Locator": "1.7.8", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.MSBuild": "4.8.0", + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyModel": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4", + "Mono.TextTemplating": "3.0.0", + "System.Text.Json": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Design.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll": { + "assemblyVersion": "9.0.0.4", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Logging/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Options/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Options.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.Primitives.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Mono.TextTemplating/3.0.0": { + "dependencies": { + "System.CodeDom": "6.0.0" + }, + "runtime": { + "lib/net6.0/Mono.TextTemplating.dll": { + "assemblyVersion": "3.0.0.0", + "fileVersion": "3.0.0.1" + } + } + }, + "Npgsql/9.0.3": { + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net8.0/Npgsql.dll": { + "assemblyVersion": "9.0.3.0", + "fileVersion": "9.0.3.0" + } + } + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.4", + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Npgsql": "9.0.3" + }, + "runtime": { + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.4.0" + } + } + }, + "System.CodeDom/6.0.0": { + "runtime": { + "lib/net6.0/System.CodeDom.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + } + }, + "System.Collections.Immutable/7.0.0": {}, + "System.Composition/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Convention": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0", + "System.Composition.TypedParts": "7.0.0" + } + }, + "System.Composition.AttributedModel/7.0.0": { + "runtime": { + "lib/net7.0/System.Composition.AttributedModel.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Convention/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.Convention.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Hosting/7.0.0": { + "dependencies": { + "System.Composition.Runtime": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.Hosting.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Runtime/7.0.0": { + "runtime": { + "lib/net7.0/System.Composition.Runtime.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.TypedParts/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.TypedParts.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.IO.Pipelines/7.0.0": {}, + "System.Reflection.Metadata/7.0.0": { + "dependencies": { + "System.Collections.Immutable": "7.0.0" + } + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": {}, + "System.Text.Json/9.0.4": {}, + "System.Threading.Channels/7.0.0": {}, + "PleasePayMe.Application/1.0.0": { + "dependencies": { + "PleasePayMe.Domain": "1.0.0" + }, + "runtime": { + "PleasePayMe.Application.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "PleasePayMe.Domain/1.0.0": { + "runtime": { + "PleasePayMe.Domain.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "PleasePayMe.Infrastructure/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Humanizer.Core/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==", + "path": "humanizer.core/2.14.1", + "hashPath": "humanizer.core.2.14.1.nupkg.sha512" + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg==", + "path": "microsoft.bcl.asyncinterfaces/7.0.0", + "hashPath": "microsoft.bcl.asyncinterfaces.7.0.0.nupkg.sha512" + }, + "Microsoft.Build.Framework/17.8.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-NrQZJW8TlKVPx72yltGb8SVz3P5mNRk9fNiD/ao8jRSk48WqIIdCn99q4IjlVmPcruuQ+yLdjNQLL8Rb4c916g==", + "path": "microsoft.build.framework/17.8.3", + "hashPath": "microsoft.build.framework.17.8.3.nupkg.sha512" + }, + "Microsoft.Build.Locator/1.7.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-sPy10x527Ph16S2u0yGME4S6ohBKJ69WfjeGG/bvELYeZVmJdKjxgnlL8cJJJLGV/cZIRqSfB12UDB8ICakOog==", + "path": "microsoft.build.locator/1.7.8", + "hashPath": "microsoft.build.locator.1.7.8.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==", + "path": "microsoft.codeanalysis.analyzers/3.3.4", + "hashPath": "microsoft.codeanalysis.analyzers.3.3.4.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==", + "path": "microsoft.codeanalysis.common/4.8.0", + "hashPath": "microsoft.codeanalysis.common.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==", + "path": "microsoft.codeanalysis.csharp/4.8.0", + "hashPath": "microsoft.codeanalysis.csharp.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3amm4tq4Lo8/BGvg9p3BJh3S9nKq2wqCXfS7138i69TUpo/bD+XvD0hNurpEBtcNZhi1FyutiomKJqVF39ugYA==", + "path": "microsoft.codeanalysis.csharp.workspaces/4.8.0", + "hashPath": "microsoft.codeanalysis.csharp.workspaces.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-LXyV+MJKsKRu3FGJA3OmSk40OUIa/dQCFLOnm5X8MNcujx7hzGu8o+zjXlb/cy5xUdZK2UKYb9YaQ2E8m9QehQ==", + "path": "microsoft.codeanalysis.workspaces.common/4.8.0", + "hashPath": "microsoft.codeanalysis.workspaces.common.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IEYreI82QZKklp54yPHxZNG9EKSK6nHEkeuf+0Asie9llgS1gp0V1hw7ODG+QyoB7MuAnNQHmeV1Per/ECpv6A==", + "path": "microsoft.codeanalysis.workspaces.msbuild/4.8.0", + "hashPath": "microsoft.codeanalysis.workspaces.msbuild.4.8.0.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+5IAX0aicQYCRfN4pAjad+JPwdEYoVEM3Z1Cl8/EiEv3FVHQHdd8TJQpQIslQDDQS/UsUMb0MsOXwqOh+TJtRw==", + "path": "microsoft.entityframeworkcore/9.0.4", + "hashPath": "microsoft.entityframeworkcore.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-E0pkWzI0liqu2ogqJ1kohk2eGkYRhf5tI75HGF6IQDARsshY/0w+prGyLvNuUeV7B8I7vYQZ4CzAKYKxw7b9gQ==", + "path": "microsoft.entityframeworkcore.abstractions/9.0.4", + "hashPath": "microsoft.entityframeworkcore.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cMsm1O7g9X5qbB2wjHf3BVVvGwkG+zeXQ+M91I1Bm6RfylFMImqBPzs0+vmuef7fPxr2yOzPhIfJ2wQJfmtaSw==", + "path": "microsoft.entityframeworkcore.analyzers/9.0.4", + "hashPath": "microsoft.entityframeworkcore.analyzers.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0NdtmsbYfMr2HyF+W6L+kPaHJl1nAmFjWj0MfI5G+CFeWZxDwltQxzzwSmZQ4QhS5z8zjczGXwHZ8e3iFaoiXA==", + "path": "microsoft.entityframeworkcore.design/9.0.4", + "hashPath": "microsoft.entityframeworkcore.design.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-OjJ+xh/wQff5b0wiC3SPvoQqTA2boZeJQf+15+3+OJPtjBKzvxuwr25QRIu1p1t+K8ryQ8pzaoZ7eOpXfNzVGA==", + "path": "microsoft.entityframeworkcore.relational/9.0.4", + "hashPath": "microsoft.entityframeworkcore.relational.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-imcZ5BGhBw5mNsWLepBbqqumWaFe0GtvyCvne2/2wsDIBRa2+Lhx4cU/pKt/4BwOizzUEOls2k1eOJQXHGMalg==", + "path": "microsoft.extensions.caching.abstractions/9.0.4", + "hashPath": "microsoft.extensions.caching.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==", + "path": "microsoft.extensions.caching.memory/9.0.4", + "hashPath": "microsoft.extensions.caching.memory.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0LN/DiIKvBrkqp7gkF3qhGIeZk6/B63PthAHjQsxymJfIBcz0kbf4/p/t4lMgggVxZ+flRi5xvTwlpPOoZk8fg==", + "path": "microsoft.extensions.configuration.abstractions/9.0.4", + "hashPath": "microsoft.extensions.configuration.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-f2MTUaS2EQ3lX4325ytPAISZqgBfXmY0WvgD80ji6Z20AoDNiCESxsqo6mFRwHJD/jfVKRw9FsW6+86gNre3ug==", + "path": "microsoft.extensions.dependencyinjection/9.0.4", + "hashPath": "microsoft.extensions.dependencyinjection.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg==", + "path": "microsoft.extensions.dependencyinjection.abstractions/9.0.4", + "hashPath": "microsoft.extensions.dependencyinjection.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ACtnvl3H3M/f8Z42980JxsNu7V9PPbzys4vBs83ZewnsgKd7JeYK18OMPo0g+MxAHrpgMrjmlinXDiaSRPcVnA==", + "path": "microsoft.extensions.dependencymodel/9.0.4", + "hashPath": "microsoft.extensions.dependencymodel.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Logging/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-xW6QPYsqhbuWBO9/1oA43g/XPKbohJx+7G8FLQgQXIriYvY7s+gxr2wjQJfRoPO900dvvv2vVH7wZovG+M1m6w==", + "path": "microsoft.extensions.logging/9.0.4", + "hashPath": "microsoft.extensions.logging.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0MXlimU4Dud6t+iNi5NEz3dO2w1HXdhoOLaYFuLPCjAsvlPQGwOT6V2KZRMLEhCAm/stSZt1AUv0XmDdkjvtbw==", + "path": "microsoft.extensions.logging.abstractions/9.0.4", + "hashPath": "microsoft.extensions.logging.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Options/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-fiFI2+58kicqVZyt/6obqoFwHiab7LC4FkQ3mmiBJ28Yy4fAvy2+v9MRnSvvlOO8chTOjKsdafFl/K9veCPo5g==", + "path": "microsoft.extensions.options/9.0.4", + "hashPath": "microsoft.extensions.options.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA==", + "path": "microsoft.extensions.primitives/9.0.4", + "hashPath": "microsoft.extensions.primitives.9.0.4.nupkg.sha512" + }, + "Mono.TextTemplating/3.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-YqueG52R/Xej4VVbKuRIodjiAhV0HR/XVbLbNrJhCZnzjnSjgMJ/dCdV0akQQxavX6hp/LC6rqLGLcXeQYU7XA==", + "path": "mono.texttemplating/3.0.0", + "hashPath": "mono.texttemplating.3.0.0.nupkg.sha512" + }, + "Npgsql/9.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-tPvY61CxOAWxNsKLEBg+oR646X4Bc8UmyQ/tJszL/7mEmIXQnnBhVJZrZEEUv0Bstu0mEsHZD5At3EO8zQRAYw==", + "path": "npgsql/9.0.3", + "hashPath": "npgsql.9.0.3.nupkg.sha512" + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-mw5vcY2IEc7L+IeGrxpp/J5OSnCcjkjAgJYCm/eD52wpZze8zsSifdqV7zXslSMmfJG2iIUGZyo3KuDtEFKwMQ==", + "path": "npgsql.entityframeworkcore.postgresql/9.0.4", + "hashPath": "npgsql.entityframeworkcore.postgresql.9.0.4.nupkg.sha512" + }, + "System.CodeDom/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==", + "path": "system.codedom/6.0.0", + "hashPath": "system.codedom.6.0.0.nupkg.sha512" + }, + "System.Collections.Immutable/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==", + "path": "system.collections.immutable/7.0.0", + "hashPath": "system.collections.immutable.7.0.0.nupkg.sha512" + }, + "System.Composition/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-tRwgcAkDd85O8Aq6zHDANzQaq380cek9lbMg5Qma46u5BZXq/G+XvIYmu+UI+BIIZ9zssXLYrkTykEqxxvhcmg==", + "path": "system.composition/7.0.0", + "hashPath": "system.composition.7.0.0.nupkg.sha512" + }, + "System.Composition.AttributedModel/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2QzClqjElKxgI1jK1Jztnq44/8DmSuTSGGahXqQ4TdEV0h9s2KikQZIgcEqVzR7OuWDFPGLHIprBJGQEPr8fAQ==", + "path": "system.composition.attributedmodel/7.0.0", + "hashPath": "system.composition.attributedmodel.7.0.0.nupkg.sha512" + }, + "System.Composition.Convention/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IMhTlpCs4HmlD8B+J8/kWfwX7vrBBOs6xyjSTzBlYSs7W4OET4tlkR/Sg9NG8jkdJH9Mymq0qGdYS1VPqRTBnQ==", + "path": "system.composition.convention/7.0.0", + "hashPath": "system.composition.convention.7.0.0.nupkg.sha512" + }, + "System.Composition.Hosting/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-eB6gwN9S+54jCTBJ5bpwMOVerKeUfGGTYCzz3QgDr1P55Gg/Wb27ShfPIhLMjmZ3MoAKu8uUSv6fcCdYJTN7Bg==", + "path": "system.composition.hosting/7.0.0", + "hashPath": "system.composition.hosting.7.0.0.nupkg.sha512" + }, + "System.Composition.Runtime/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-aZJ1Zr5Txe925rbo4742XifEyW0MIni1eiUebmcrP3HwLXZ3IbXUj4MFMUH/RmnJOAQiS401leg/2Sz1MkApDw==", + "path": "system.composition.runtime/7.0.0", + "hashPath": "system.composition.runtime.7.0.0.nupkg.sha512" + }, + "System.Composition.TypedParts/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ZK0KNPfbtxVceTwh+oHNGUOYV2WNOHReX2AXipuvkURC7s/jPwoWfsu3SnDBDgofqbiWr96geofdQ2erm/KTHg==", + "path": "system.composition.typedparts/7.0.0", + "hashPath": "system.composition.typedparts.7.0.0.nupkg.sha512" + }, + "System.IO.Pipelines/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg==", + "path": "system.io.pipelines/7.0.0", + "hashPath": "system.io.pipelines.7.0.0.nupkg.sha512" + }, + "System.Reflection.Metadata/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==", + "path": "system.reflection.metadata/7.0.0", + "hashPath": "system.reflection.metadata.7.0.0.nupkg.sha512" + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + "path": "system.runtime.compilerservices.unsafe/6.0.0", + "hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512" + }, + "System.Text.Json/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-pYtmpcO6R3Ef1XilZEHgXP2xBPVORbYEzRP7dl0IAAbN8Dm+kfwio8aCKle97rAWXOExr292MuxWYurIuwN62g==", + "path": "system.text.json/9.0.4", + "hashPath": "system.text.json.9.0.4.nupkg.sha512" + }, + "System.Threading.Channels/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA==", + "path": "system.threading.channels/7.0.0", + "hashPath": "system.threading.channels.7.0.0.nupkg.sha512" + }, + "PleasePayMe.Application/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.dll b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.dll new file mode 100644 index 0000000..746f02f Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.dll differ diff --git a/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.pdb b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.pdb new file mode 100644 index 0000000..583ebf2 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.pdb differ diff --git a/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.runtimeconfig.json b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.runtimeconfig.json new file mode 100644 index 0000000..c5de900 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/bin/Debug/net9.0/PleasePayMe.Infrastructure.runtimeconfig.json @@ -0,0 +1,13 @@ +{ + "runtimeOptions": { + "tfm": "net9.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "9.0.0" + }, + "configProperties": { + "System.Reflection.NullabilityInfoContext.IsSupported": true, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Application.dll b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Application.dll new file mode 100644 index 0000000..8eec7c1 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Application.dll differ diff --git a/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Application.pdb b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Application.pdb new file mode 100644 index 0000000..5bc04bf Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Application.pdb differ diff --git a/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Domain.dll b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Domain.dll new file mode 100644 index 0000000..0c885d2 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Domain.dll differ diff --git a/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Domain.pdb b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Domain.pdb new file mode 100644 index 0000000..db59d49 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Domain.pdb differ diff --git a/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.deps.json b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.deps.json new file mode 100644 index 0000000..d46a01a --- /dev/null +++ b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.deps.json @@ -0,0 +1,930 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v9.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v9.0": { + "PleasePayMe.Infrastructure/1.0.0": { + "dependencies": { + "Microsoft.EntityFrameworkCore.Design": "9.0.4", + "Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.4", + "PleasePayMe.Application": "1.0.0", + "PleasePayMe.Domain": "1.0.0" + }, + "runtime": { + "PleasePayMe.Infrastructure.dll": {} + } + }, + "Humanizer.Core/2.14.1": { + "runtime": { + "lib/net6.0/Humanizer.dll": { + "assemblyVersion": "2.14.0.0", + "fileVersion": "2.14.1.48190" + } + } + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "runtime": { + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "Microsoft.Build.Framework/17.8.3": {}, + "Microsoft.Build.Locator/1.7.8": { + "runtime": { + "lib/net6.0/Microsoft.Build.Locator.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.7.8.28074" + } + } + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": {}, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.4", + "System.Collections.Immutable": "7.0.0", + "System.Reflection.Metadata": "7.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "dependencies": { + "Microsoft.CodeAnalysis.Common": "4.8.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.Common": "4.8.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Bcl.AsyncInterfaces": "7.0.0", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "System.Composition": "7.0.0", + "System.IO.Pipelines": "7.0.0", + "System.Threading.Channels": "7.0.0" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "dependencies": { + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.CodeAnalysis.Common": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.Common": "4.8.0", + "System.Text.Json": "9.0.4" + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + }, + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll": { + "assemblyVersion": "4.8.0.0", + "fileVersion": "4.800.23.55801" + } + }, + "resources": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "9.0.4", + "Microsoft.EntityFrameworkCore.Analyzers": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": {}, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.Build.Locator": "1.7.8", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.MSBuild": "4.8.0", + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyModel": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4", + "Mono.TextTemplating": "3.0.0", + "System.Text.Json": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Design.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.425.16310" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll": { + "assemblyVersion": "9.0.0.4", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Logging/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Options/9.0.4": { + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Options.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "runtime": { + "lib/net9.0/Microsoft.Extensions.Primitives.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.425.16305" + } + } + }, + "Mono.TextTemplating/3.0.0": { + "dependencies": { + "System.CodeDom": "6.0.0" + }, + "runtime": { + "lib/net6.0/Mono.TextTemplating.dll": { + "assemblyVersion": "3.0.0.0", + "fileVersion": "3.0.0.1" + } + } + }, + "Npgsql/9.0.3": { + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "9.0.4" + }, + "runtime": { + "lib/net8.0/Npgsql.dll": { + "assemblyVersion": "9.0.3.0", + "fileVersion": "9.0.3.0" + } + } + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.4", + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Npgsql": "9.0.3" + }, + "runtime": { + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll": { + "assemblyVersion": "9.0.4.0", + "fileVersion": "9.0.4.0" + } + } + }, + "System.CodeDom/6.0.0": { + "runtime": { + "lib/net6.0/System.CodeDom.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.21.52210" + } + } + }, + "System.Collections.Immutable/7.0.0": {}, + "System.Composition/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Convention": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0", + "System.Composition.TypedParts": "7.0.0" + } + }, + "System.Composition.AttributedModel/7.0.0": { + "runtime": { + "lib/net7.0/System.Composition.AttributedModel.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Convention/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.Convention.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Hosting/7.0.0": { + "dependencies": { + "System.Composition.Runtime": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.Hosting.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.Runtime/7.0.0": { + "runtime": { + "lib/net7.0/System.Composition.Runtime.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.Composition.TypedParts/7.0.0": { + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0" + }, + "runtime": { + "lib/net7.0/System.Composition.TypedParts.dll": { + "assemblyVersion": "7.0.0.0", + "fileVersion": "7.0.22.51805" + } + } + }, + "System.IO.Pipelines/7.0.0": {}, + "System.Reflection.Metadata/7.0.0": { + "dependencies": { + "System.Collections.Immutable": "7.0.0" + } + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": {}, + "System.Text.Json/9.0.4": {}, + "System.Threading.Channels/7.0.0": {}, + "PleasePayMe.Application/1.0.0": { + "dependencies": { + "PleasePayMe.Domain": "1.0.0" + }, + "runtime": { + "PleasePayMe.Application.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "PleasePayMe.Domain/1.0.0": { + "runtime": { + "PleasePayMe.Domain.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + } + } + }, + "libraries": { + "PleasePayMe.Infrastructure/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Humanizer.Core/2.14.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==", + "path": "humanizer.core/2.14.1", + "hashPath": "humanizer.core.2.14.1.nupkg.sha512" + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg==", + "path": "microsoft.bcl.asyncinterfaces/7.0.0", + "hashPath": "microsoft.bcl.asyncinterfaces.7.0.0.nupkg.sha512" + }, + "Microsoft.Build.Framework/17.8.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-NrQZJW8TlKVPx72yltGb8SVz3P5mNRk9fNiD/ao8jRSk48WqIIdCn99q4IjlVmPcruuQ+yLdjNQLL8Rb4c916g==", + "path": "microsoft.build.framework/17.8.3", + "hashPath": "microsoft.build.framework.17.8.3.nupkg.sha512" + }, + "Microsoft.Build.Locator/1.7.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-sPy10x527Ph16S2u0yGME4S6ohBKJ69WfjeGG/bvELYeZVmJdKjxgnlL8cJJJLGV/cZIRqSfB12UDB8ICakOog==", + "path": "microsoft.build.locator/1.7.8", + "hashPath": "microsoft.build.locator.1.7.8.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==", + "path": "microsoft.codeanalysis.analyzers/3.3.4", + "hashPath": "microsoft.codeanalysis.analyzers.3.3.4.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==", + "path": "microsoft.codeanalysis.common/4.8.0", + "hashPath": "microsoft.codeanalysis.common.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==", + "path": "microsoft.codeanalysis.csharp/4.8.0", + "hashPath": "microsoft.codeanalysis.csharp.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3amm4tq4Lo8/BGvg9p3BJh3S9nKq2wqCXfS7138i69TUpo/bD+XvD0hNurpEBtcNZhi1FyutiomKJqVF39ugYA==", + "path": "microsoft.codeanalysis.csharp.workspaces/4.8.0", + "hashPath": "microsoft.codeanalysis.csharp.workspaces.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-LXyV+MJKsKRu3FGJA3OmSk40OUIa/dQCFLOnm5X8MNcujx7hzGu8o+zjXlb/cy5xUdZK2UKYb9YaQ2E8m9QehQ==", + "path": "microsoft.codeanalysis.workspaces.common/4.8.0", + "hashPath": "microsoft.codeanalysis.workspaces.common.4.8.0.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IEYreI82QZKklp54yPHxZNG9EKSK6nHEkeuf+0Asie9llgS1gp0V1hw7ODG+QyoB7MuAnNQHmeV1Per/ECpv6A==", + "path": "microsoft.codeanalysis.workspaces.msbuild/4.8.0", + "hashPath": "microsoft.codeanalysis.workspaces.msbuild.4.8.0.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-+5IAX0aicQYCRfN4pAjad+JPwdEYoVEM3Z1Cl8/EiEv3FVHQHdd8TJQpQIslQDDQS/UsUMb0MsOXwqOh+TJtRw==", + "path": "microsoft.entityframeworkcore/9.0.4", + "hashPath": "microsoft.entityframeworkcore.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-E0pkWzI0liqu2ogqJ1kohk2eGkYRhf5tI75HGF6IQDARsshY/0w+prGyLvNuUeV7B8I7vYQZ4CzAKYKxw7b9gQ==", + "path": "microsoft.entityframeworkcore.abstractions/9.0.4", + "hashPath": "microsoft.entityframeworkcore.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-cMsm1O7g9X5qbB2wjHf3BVVvGwkG+zeXQ+M91I1Bm6RfylFMImqBPzs0+vmuef7fPxr2yOzPhIfJ2wQJfmtaSw==", + "path": "microsoft.entityframeworkcore.analyzers/9.0.4", + "hashPath": "microsoft.entityframeworkcore.analyzers.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0NdtmsbYfMr2HyF+W6L+kPaHJl1nAmFjWj0MfI5G+CFeWZxDwltQxzzwSmZQ4QhS5z8zjczGXwHZ8e3iFaoiXA==", + "path": "microsoft.entityframeworkcore.design/9.0.4", + "hashPath": "microsoft.entityframeworkcore.design.9.0.4.nupkg.sha512" + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-OjJ+xh/wQff5b0wiC3SPvoQqTA2boZeJQf+15+3+OJPtjBKzvxuwr25QRIu1p1t+K8ryQ8pzaoZ7eOpXfNzVGA==", + "path": "microsoft.entityframeworkcore.relational/9.0.4", + "hashPath": "microsoft.entityframeworkcore.relational.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-imcZ5BGhBw5mNsWLepBbqqumWaFe0GtvyCvne2/2wsDIBRa2+Lhx4cU/pKt/4BwOizzUEOls2k1eOJQXHGMalg==", + "path": "microsoft.extensions.caching.abstractions/9.0.4", + "hashPath": "microsoft.extensions.caching.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==", + "path": "microsoft.extensions.caching.memory/9.0.4", + "hashPath": "microsoft.extensions.caching.memory.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0LN/DiIKvBrkqp7gkF3qhGIeZk6/B63PthAHjQsxymJfIBcz0kbf4/p/t4lMgggVxZ+flRi5xvTwlpPOoZk8fg==", + "path": "microsoft.extensions.configuration.abstractions/9.0.4", + "hashPath": "microsoft.extensions.configuration.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-f2MTUaS2EQ3lX4325ytPAISZqgBfXmY0WvgD80ji6Z20AoDNiCESxsqo6mFRwHJD/jfVKRw9FsW6+86gNre3ug==", + "path": "microsoft.extensions.dependencyinjection/9.0.4", + "hashPath": "microsoft.extensions.dependencyinjection.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg==", + "path": "microsoft.extensions.dependencyinjection.abstractions/9.0.4", + "hashPath": "microsoft.extensions.dependencyinjection.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ACtnvl3H3M/f8Z42980JxsNu7V9PPbzys4vBs83ZewnsgKd7JeYK18OMPo0g+MxAHrpgMrjmlinXDiaSRPcVnA==", + "path": "microsoft.extensions.dependencymodel/9.0.4", + "hashPath": "microsoft.extensions.dependencymodel.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Logging/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-xW6QPYsqhbuWBO9/1oA43g/XPKbohJx+7G8FLQgQXIriYvY7s+gxr2wjQJfRoPO900dvvv2vVH7wZovG+M1m6w==", + "path": "microsoft.extensions.logging/9.0.4", + "hashPath": "microsoft.extensions.logging.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-0MXlimU4Dud6t+iNi5NEz3dO2w1HXdhoOLaYFuLPCjAsvlPQGwOT6V2KZRMLEhCAm/stSZt1AUv0XmDdkjvtbw==", + "path": "microsoft.extensions.logging.abstractions/9.0.4", + "hashPath": "microsoft.extensions.logging.abstractions.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Options/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-fiFI2+58kicqVZyt/6obqoFwHiab7LC4FkQ3mmiBJ28Yy4fAvy2+v9MRnSvvlOO8chTOjKsdafFl/K9veCPo5g==", + "path": "microsoft.extensions.options/9.0.4", + "hashPath": "microsoft.extensions.options.9.0.4.nupkg.sha512" + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA==", + "path": "microsoft.extensions.primitives/9.0.4", + "hashPath": "microsoft.extensions.primitives.9.0.4.nupkg.sha512" + }, + "Mono.TextTemplating/3.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-YqueG52R/Xej4VVbKuRIodjiAhV0HR/XVbLbNrJhCZnzjnSjgMJ/dCdV0akQQxavX6hp/LC6rqLGLcXeQYU7XA==", + "path": "mono.texttemplating/3.0.0", + "hashPath": "mono.texttemplating.3.0.0.nupkg.sha512" + }, + "Npgsql/9.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-tPvY61CxOAWxNsKLEBg+oR646X4Bc8UmyQ/tJszL/7mEmIXQnnBhVJZrZEEUv0Bstu0mEsHZD5At3EO8zQRAYw==", + "path": "npgsql/9.0.3", + "hashPath": "npgsql.9.0.3.nupkg.sha512" + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-mw5vcY2IEc7L+IeGrxpp/J5OSnCcjkjAgJYCm/eD52wpZze8zsSifdqV7zXslSMmfJG2iIUGZyo3KuDtEFKwMQ==", + "path": "npgsql.entityframeworkcore.postgresql/9.0.4", + "hashPath": "npgsql.entityframeworkcore.postgresql.9.0.4.nupkg.sha512" + }, + "System.CodeDom/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==", + "path": "system.codedom/6.0.0", + "hashPath": "system.codedom.6.0.0.nupkg.sha512" + }, + "System.Collections.Immutable/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==", + "path": "system.collections.immutable/7.0.0", + "hashPath": "system.collections.immutable.7.0.0.nupkg.sha512" + }, + "System.Composition/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-tRwgcAkDd85O8Aq6zHDANzQaq380cek9lbMg5Qma46u5BZXq/G+XvIYmu+UI+BIIZ9zssXLYrkTykEqxxvhcmg==", + "path": "system.composition/7.0.0", + "hashPath": "system.composition.7.0.0.nupkg.sha512" + }, + "System.Composition.AttributedModel/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2QzClqjElKxgI1jK1Jztnq44/8DmSuTSGGahXqQ4TdEV0h9s2KikQZIgcEqVzR7OuWDFPGLHIprBJGQEPr8fAQ==", + "path": "system.composition.attributedmodel/7.0.0", + "hashPath": "system.composition.attributedmodel.7.0.0.nupkg.sha512" + }, + "System.Composition.Convention/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IMhTlpCs4HmlD8B+J8/kWfwX7vrBBOs6xyjSTzBlYSs7W4OET4tlkR/Sg9NG8jkdJH9Mymq0qGdYS1VPqRTBnQ==", + "path": "system.composition.convention/7.0.0", + "hashPath": "system.composition.convention.7.0.0.nupkg.sha512" + }, + "System.Composition.Hosting/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-eB6gwN9S+54jCTBJ5bpwMOVerKeUfGGTYCzz3QgDr1P55Gg/Wb27ShfPIhLMjmZ3MoAKu8uUSv6fcCdYJTN7Bg==", + "path": "system.composition.hosting/7.0.0", + "hashPath": "system.composition.hosting.7.0.0.nupkg.sha512" + }, + "System.Composition.Runtime/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-aZJ1Zr5Txe925rbo4742XifEyW0MIni1eiUebmcrP3HwLXZ3IbXUj4MFMUH/RmnJOAQiS401leg/2Sz1MkApDw==", + "path": "system.composition.runtime/7.0.0", + "hashPath": "system.composition.runtime.7.0.0.nupkg.sha512" + }, + "System.Composition.TypedParts/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ZK0KNPfbtxVceTwh+oHNGUOYV2WNOHReX2AXipuvkURC7s/jPwoWfsu3SnDBDgofqbiWr96geofdQ2erm/KTHg==", + "path": "system.composition.typedparts/7.0.0", + "hashPath": "system.composition.typedparts.7.0.0.nupkg.sha512" + }, + "System.IO.Pipelines/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg==", + "path": "system.io.pipelines/7.0.0", + "hashPath": "system.io.pipelines.7.0.0.nupkg.sha512" + }, + "System.Reflection.Metadata/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==", + "path": "system.reflection.metadata/7.0.0", + "hashPath": "system.reflection.metadata.7.0.0.nupkg.sha512" + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + "path": "system.runtime.compilerservices.unsafe/6.0.0", + "hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512" + }, + "System.Text.Json/9.0.4": { + "type": "package", + "serviceable": true, + "sha512": "sha512-pYtmpcO6R3Ef1XilZEHgXP2xBPVORbYEzRP7dl0IAAbN8Dm+kfwio8aCKle97rAWXOExr292MuxWYurIuwN62g==", + "path": "system.text.json/9.0.4", + "hashPath": "system.text.json.9.0.4.nupkg.sha512" + }, + "System.Threading.Channels/7.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA==", + "path": "system.threading.channels/7.0.0", + "hashPath": "system.threading.channels.7.0.0.nupkg.sha512" + }, + "PleasePayMe.Application/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.dll b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.dll new file mode 100644 index 0000000..d1ecdaf Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.dll differ diff --git a/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.pdb b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.pdb new file mode 100644 index 0000000..d918482 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.pdb differ diff --git a/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.runtimeconfig.json b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.runtimeconfig.json new file mode 100644 index 0000000..b72259a --- /dev/null +++ b/src/PleasePayMe.Infrastructure/bin/Release/net9.0/PleasePayMe.Infrastructure.runtimeconfig.json @@ -0,0 +1,14 @@ +{ + "runtimeOptions": { + "tfm": "net9.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "9.0.0" + }, + "configProperties": { + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Reflection.NullabilityInfoContext.IsSupported": true, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePa.315EE259.Up2Date b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePa.315EE259.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.AssemblyInfo.cs b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.AssemblyInfo.cs new file mode 100644 index 0000000..e71953c --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("PleasePayMe.Infrastructure")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("PleasePayMe.Infrastructure")] +[assembly: System.Reflection.AssemblyTitleAttribute("PleasePayMe.Infrastructure")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.AssemblyInfoInputs.cache b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.AssemblyInfoInputs.cache new file mode 100644 index 0000000..e034979 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +9748e364b58181c537327729af963ff9e2bd1f3350d1bfd389e65cae33839526 diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.GeneratedMSBuildEditorConfig.editorconfig b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..81e073f --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,23 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = PleasePayMe.Infrastructure +build_property.ProjectDir = c:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.GlobalUsings.g.cs b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.assets.cache b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.assets.cache new file mode 100644 index 0000000..d72ce37 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.assets.cache differ diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.csproj.AssemblyReference.cache b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.csproj.AssemblyReference.cache new file mode 100644 index 0000000..c80e804 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.csproj.AssemblyReference.cache differ diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.csproj.CoreCompileInputs.cache b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..19cd361 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +1d045f3f106ea838d60e8dde2eab7b5a97e31374c8f73914aeb25026b366374b diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.csproj.FileListAbsolute.txt b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..b906d1a --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.csproj.FileListAbsolute.txt @@ -0,0 +1,19 @@ +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Debug\net9.0\PleasePayMe.Infrastructure.deps.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Debug\net9.0\PleasePayMe.Infrastructure.runtimeconfig.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Debug\net9.0\PleasePayMe.Infrastructure.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Debug\net9.0\PleasePayMe.Infrastructure.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Debug\net9.0\PleasePayMe.Application.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Debug\net9.0\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Debug\net9.0\PleasePayMe.Application.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Debug\net9.0\PleasePayMe.Domain.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\PleasePayMe.Infrastructure.csproj.AssemblyReference.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\PleasePayMe.Infrastructure.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\PleasePayMe.Infrastructure.AssemblyInfoInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\PleasePayMe.Infrastructure.AssemblyInfo.cs +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\PleasePayMe.Infrastructure.csproj.CoreCompileInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\PleasePa.315EE259.Up2Date +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\PleasePayMe.Infrastructure.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\refint\PleasePayMe.Infrastructure.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\PleasePayMe.Infrastructure.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\PleasePayMe.Infrastructure.genruntimeconfig.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Debug\net9.0\ref\PleasePayMe.Infrastructure.dll diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.dll b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.dll new file mode 100644 index 0000000..746f02f Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.dll differ diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.genruntimeconfig.cache b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.genruntimeconfig.cache new file mode 100644 index 0000000..5bcae33 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.genruntimeconfig.cache @@ -0,0 +1 @@ +62c8f971e33a21793b547fab4469d244bcb00d3dde7712dcc8d7724ff0746df4 diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.pdb b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.pdb new file mode 100644 index 0000000..583ebf2 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/PleasePayMe.Infrastructure.pdb differ diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/ref/PleasePayMe.Infrastructure.dll b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/ref/PleasePayMe.Infrastructure.dll new file mode 100644 index 0000000..3b9a9ed Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/ref/PleasePayMe.Infrastructure.dll differ diff --git a/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/refint/PleasePayMe.Infrastructure.dll b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/refint/PleasePayMe.Infrastructure.dll new file mode 100644 index 0000000..3b9a9ed Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Debug/net9.0/refint/PleasePayMe.Infrastructure.dll differ diff --git a/src/PleasePayMe.Infrastructure/obj/PleasePayMe.Infrastructure.csproj.nuget.dgspec.json b/src/PleasePayMe.Infrastructure/obj/PleasePayMe.Infrastructure.csproj.nuget.dgspec.json new file mode 100644 index 0000000..2099990 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/PleasePayMe.Infrastructure.csproj.nuget.dgspec.json @@ -0,0 +1,208 @@ +{ + "format": 1, + "restore": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj": {} + }, + "projects": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj", + "projectName": "PleasePayMe.Application", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "projectName": "PleasePayMe.Domain", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj", + "projectName": "PleasePayMe.Infrastructure", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj" + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "dependencies": { + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[9.0.4, )" + }, + "Npgsql.EntityFrameworkCore.PostgreSQL": { + "target": "Package", + "version": "[9.0.4, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Infrastructure/obj/PleasePayMe.Infrastructure.csproj.nuget.g.props b/src/PleasePayMe.Infrastructure/obj/PleasePayMe.Infrastructure.csproj.nuget.g.props new file mode 100644 index 0000000..49de9eb --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/PleasePayMe.Infrastructure.csproj.nuget.g.props @@ -0,0 +1,23 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget + C:\Users\ggpo1\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget + PackageReference + 6.14.3 + + + + + + + + + + + C:\Users\ggpo1\AppData\Local\Temp\cursor-sandbox-cache\6665f3d9344be5b329c3ede124b2a60a\nuget\microsoft.codeanalysis.analyzers\3.3.4 + + \ No newline at end of file diff --git a/src/PleasePayMe.Infrastructure/obj/PleasePayMe.Infrastructure.csproj.nuget.g.targets b/src/PleasePayMe.Infrastructure/obj/PleasePayMe.Infrastructure.csproj.nuget.g.targets new file mode 100644 index 0000000..6c9ca84 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/PleasePayMe.Infrastructure.csproj.nuget.g.targets @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs new file mode 100644 index 0000000..feda5e9 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")] diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePa.315EE259.Up2Date b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePa.315EE259.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.AssemblyInfo.cs b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.AssemblyInfo.cs new file mode 100644 index 0000000..bef3f5b --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("PleasePayMe.Infrastructure")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("PleasePayMe.Infrastructure")] +[assembly: System.Reflection.AssemblyTitleAttribute("PleasePayMe.Infrastructure")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Создано классом WriteCodeFragment MSBuild. + diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.AssemblyInfoInputs.cache b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.AssemblyInfoInputs.cache new file mode 100644 index 0000000..00b79e9 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +43de54a7369fcf8be0d9596c88579fa5a9acb7706c41ce5eb419b530c613baef diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.GeneratedMSBuildEditorConfig.editorconfig b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..95395c3 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,23 @@ +is_global = true +build_property.TargetFramework = net9.0 +build_property.TargetFramework = net9.0 +build_property.TargetPlatformMinVersion = +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = PleasePayMe.Infrastructure +build_property.ProjectDir = C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = +build_property.EffectiveAnalysisLevelStyle = 9.0 +build_property.EnableCodeStyleSeverity = diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.GlobalUsings.g.cs b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.assets.cache b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.assets.cache new file mode 100644 index 0000000..bf26f4e Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.assets.cache differ diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.csproj.AssemblyReference.cache b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.csproj.AssemblyReference.cache new file mode 100644 index 0000000..98e4e86 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.csproj.AssemblyReference.cache differ diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.csproj.CoreCompileInputs.cache b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..0547eed --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +1a350a079e42e0d2403cb07e7f4234e9491c1c633aeeb278135fa55632a74648 diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.csproj.FileListAbsolute.txt b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..04cc21e --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.csproj.FileListAbsolute.txt @@ -0,0 +1,19 @@ +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Release\net9.0\PleasePayMe.Infrastructure.deps.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Release\net9.0\PleasePayMe.Infrastructure.runtimeconfig.json +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Release\net9.0\PleasePayMe.Infrastructure.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Release\net9.0\PleasePayMe.Infrastructure.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Release\net9.0\PleasePayMe.Application.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Release\net9.0\PleasePayMe.Domain.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Release\net9.0\PleasePayMe.Application.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\bin\Release\net9.0\PleasePayMe.Domain.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\PleasePayMe.Infrastructure.csproj.AssemblyReference.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\PleasePayMe.Infrastructure.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\PleasePayMe.Infrastructure.AssemblyInfoInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\PleasePayMe.Infrastructure.AssemblyInfo.cs +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\PleasePayMe.Infrastructure.csproj.CoreCompileInputs.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\PleasePa.315EE259.Up2Date +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\PleasePayMe.Infrastructure.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\refint\PleasePayMe.Infrastructure.dll +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\PleasePayMe.Infrastructure.pdb +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\PleasePayMe.Infrastructure.genruntimeconfig.cache +C:\Users\ggpo1\Desktop\please_pay_me_bot\src\PleasePayMe.Infrastructure\obj\Release\net9.0\ref\PleasePayMe.Infrastructure.dll diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.dll b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.dll new file mode 100644 index 0000000..d1ecdaf Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.dll differ diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.genruntimeconfig.cache b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.genruntimeconfig.cache new file mode 100644 index 0000000..92c1188 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.genruntimeconfig.cache @@ -0,0 +1 @@ +433557d491286b6ada18c1694a954129db3f45ba612ca325eed255ff457ed9cc diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.pdb b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.pdb new file mode 100644 index 0000000..d918482 Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/PleasePayMe.Infrastructure.pdb differ diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/ref/PleasePayMe.Infrastructure.dll b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/ref/PleasePayMe.Infrastructure.dll new file mode 100644 index 0000000..20bb95b Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/ref/PleasePayMe.Infrastructure.dll differ diff --git a/src/PleasePayMe.Infrastructure/obj/Release/net9.0/refint/PleasePayMe.Infrastructure.dll b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/refint/PleasePayMe.Infrastructure.dll new file mode 100644 index 0000000..20bb95b Binary files /dev/null and b/src/PleasePayMe.Infrastructure/obj/Release/net9.0/refint/PleasePayMe.Infrastructure.dll differ diff --git a/src/PleasePayMe.Infrastructure/obj/project.assets.json b/src/PleasePayMe.Infrastructure/obj/project.assets.json new file mode 100644 index 0000000..32a72c5 --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/project.assets.json @@ -0,0 +1,2900 @@ +{ + "version": 3, + "targets": { + "net9.0": { + "Humanizer.Core/2.14.1": { + "type": "package", + "compile": { + "lib/net6.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/Humanizer.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "type": "package", + "compile": { + "lib/netstandard2.1/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Build.Framework/17.8.3": { + "type": "package", + "compile": { + "ref/net8.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/_._": { + "related": ".pdb;.xml" + } + } + }, + "Microsoft.Build.Locator/1.7.8": { + "type": "package", + "compile": { + "lib/net6.0/_._": {} + }, + "runtime": { + "lib/net6.0/Microsoft.Build.Locator.dll": {} + }, + "build": { + "build/_._": {} + } + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": { + "type": "package", + "build": { + "buildTransitive/Microsoft.CodeAnalysis.Analyzers.props": {}, + "buildTransitive/Microsoft.CodeAnalysis.Analyzers.targets": {} + } + }, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "type": "package", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.4", + "System.Collections.Immutable": "7.0.0", + "System.Reflection.Metadata": "7.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.dll": { + "related": ".pdb;.xml" + } + }, + "resource": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "type": "package", + "dependencies": { + "Microsoft.CodeAnalysis.Common": "[4.8.0]" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.dll": { + "related": ".pdb;.xml" + } + }, + "resource": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "type": "package", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.CodeAnalysis.CSharp": "[4.8.0]", + "Microsoft.CodeAnalysis.Common": "[4.8.0]", + "Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll": { + "related": ".pdb;.xml" + } + }, + "resource": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "type": "package", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Bcl.AsyncInterfaces": "7.0.0", + "Microsoft.CodeAnalysis.Common": "[4.8.0]", + "System.Composition": "7.0.0", + "System.IO.Pipelines": "7.0.0", + "System.Threading.Channels": "7.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".pdb;.xml" + } + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.dll": { + "related": ".pdb;.xml" + } + }, + "resource": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "type": "package", + "dependencies": { + "Microsoft.Build.Framework": "16.10.0", + "Microsoft.CodeAnalysis.Common": "[4.8.0]", + "Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]", + "System.Text.Json": "7.0.3" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".pdb;.runtimeconfig.json;.xml" + } + }, + "runtime": { + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll": { + "related": ".pdb;.runtimeconfig.json;.xml" + }, + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll": { + "related": ".BuildHost.pdb;.BuildHost.runtimeconfig.json;.BuildHost.xml;.pdb;.xml" + } + }, + "resource": { + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "cs" + }, + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "de" + }, + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "es" + }, + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "fr" + }, + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "it" + }, + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ja" + }, + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ko" + }, + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pl" + }, + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pt-BR" + }, + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ru" + }, + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "tr" + }, + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hans" + }, + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "9.0.4", + "Microsoft.EntityFrameworkCore.Analyzers": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "compile": { + "lib/net8.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.EntityFrameworkCore.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "type": "package", + "compile": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { + "related": ".xml" + } + } + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": { + "type": "package" + }, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "type": "package", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.Build.Locator": "1.7.8", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "4.8.0", + "Microsoft.CodeAnalysis.Workspaces.MSBuild": "4.8.0", + "Microsoft.EntityFrameworkCore.Relational": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyModel": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4", + "Mono.TextTemplating": "3.0.0", + "System.Text.Json": "9.0.4" + }, + "compile": { + "lib/net8.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Design.dll": { + "related": ".xml" + } + }, + "build": { + "build/net8.0/Microsoft.EntityFrameworkCore.Design.props": {} + } + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging": "9.0.4" + }, + "compile": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": { + "related": ".xml" + } + } + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "type": "package", + "compile": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "type": "package", + "compile": { + "lib/net9.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets": {} + } + }, + "Microsoft.Extensions.Options/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + }, + "compile": { + "lib/net9.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Options.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets": {} + } + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "type": "package", + "compile": { + "lib/net9.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/Microsoft.Extensions.Primitives.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/_._": {} + } + }, + "Mono.TextTemplating/3.0.0": { + "type": "package", + "dependencies": { + "System.CodeDom": "6.0.0" + }, + "compile": { + "lib/net6.0/_._": {} + }, + "runtime": { + "lib/net6.0/Mono.TextTemplating.dll": {} + }, + "build": { + "buildTransitive/Mono.TextTemplating.targets": {} + } + }, + "Npgsql/9.0.3": { + "type": "package", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "8.0.2" + }, + "compile": { + "lib/net8.0/Npgsql.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Npgsql.dll": { + "related": ".xml" + } + } + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "type": "package", + "dependencies": { + "Microsoft.EntityFrameworkCore": "[9.0.1, 10.0.0)", + "Microsoft.EntityFrameworkCore.Relational": "[9.0.1, 10.0.0)", + "Npgsql": "9.0.3" + }, + "compile": { + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll": { + "related": ".xml" + } + } + }, + "System.CodeDom/6.0.0": { + "type": "package", + "compile": { + "lib/net6.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/System.CodeDom.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/netcoreapp3.1/_._": {} + } + }, + "System.Collections.Immutable/7.0.0": { + "type": "package", + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Collections.Immutable.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition/7.0.0": { + "type": "package", + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Convention": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0", + "System.Composition.TypedParts": "7.0.0" + }, + "compile": { + "lib/netcoreapp2.0/_._": {} + }, + "runtime": { + "lib/netcoreapp2.0/_._": {} + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition.AttributedModel/7.0.0": { + "type": "package", + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Composition.AttributedModel.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition.Convention/7.0.0": { + "type": "package", + "dependencies": { + "System.Composition.AttributedModel": "7.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Composition.Convention.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition.Hosting/7.0.0": { + "type": "package", + "dependencies": { + "System.Composition.Runtime": "7.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Composition.Hosting.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition.Runtime/7.0.0": { + "type": "package", + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Composition.Runtime.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Composition.TypedParts/7.0.0": { + "type": "package", + "dependencies": { + "System.Composition.AttributedModel": "7.0.0", + "System.Composition.Hosting": "7.0.0", + "System.Composition.Runtime": "7.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Composition.TypedParts.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.IO.Pipelines/7.0.0": { + "type": "package", + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.IO.Pipelines.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Reflection.Metadata/7.0.0": { + "type": "package", + "dependencies": { + "System.Collections.Immutable": "7.0.0" + }, + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Reflection.Metadata.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "type": "package", + "compile": { + "lib/net6.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/netcoreapp3.1/_._": {} + } + }, + "System.Text.Json/9.0.4": { + "type": "package", + "compile": { + "lib/net9.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net9.0/System.Text.Json.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net8.0/System.Text.Json.targets": {} + } + }, + "System.Threading.Channels/7.0.0": { + "type": "package", + "compile": { + "lib/net7.0/_._": { + "related": ".xml" + } + }, + "runtime": { + "lib/net7.0/System.Threading.Channels.dll": { + "related": ".xml" + } + }, + "build": { + "buildTransitive/net6.0/_._": {} + } + }, + "PleasePayMe.Application/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v9.0", + "dependencies": { + "PleasePayMe.Domain": "1.0.0" + }, + "compile": { + "bin/placeholder/PleasePayMe.Application.dll": {} + }, + "runtime": { + "bin/placeholder/PleasePayMe.Application.dll": {} + } + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "framework": ".NETCoreApp,Version=v9.0", + "compile": { + "bin/placeholder/PleasePayMe.Domain.dll": {} + }, + "runtime": { + "bin/placeholder/PleasePayMe.Domain.dll": {} + } + } + } + }, + "libraries": { + "Humanizer.Core/2.14.1": { + "sha512": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==", + "type": "package", + "path": "humanizer.core/2.14.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "humanizer.core.2.14.1.nupkg.sha512", + "humanizer.core.nuspec", + "lib/net6.0/Humanizer.dll", + "lib/net6.0/Humanizer.xml", + "lib/netstandard1.0/Humanizer.dll", + "lib/netstandard1.0/Humanizer.xml", + "lib/netstandard2.0/Humanizer.dll", + "lib/netstandard2.0/Humanizer.xml", + "logo.png" + ] + }, + "Microsoft.Bcl.AsyncInterfaces/7.0.0": { + "sha512": "3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg==", + "type": "package", + "path": "microsoft.bcl.asyncinterfaces/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Bcl.AsyncInterfaces.targets", + "buildTransitive/net462/_._", + "lib/net462/Microsoft.Bcl.AsyncInterfaces.dll", + "lib/net462/Microsoft.Bcl.AsyncInterfaces.xml", + "lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll", + "lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.xml", + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll", + "lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.xml", + "microsoft.bcl.asyncinterfaces.7.0.0.nupkg.sha512", + "microsoft.bcl.asyncinterfaces.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Build.Framework/17.8.3": { + "sha512": "NrQZJW8TlKVPx72yltGb8SVz3P5mNRk9fNiD/ao8jRSk48WqIIdCn99q4IjlVmPcruuQ+yLdjNQLL8Rb4c916g==", + "type": "package", + "path": "microsoft.build.framework/17.8.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "MSBuild-NuGet-Icon.png", + "README.md", + "lib/net472/Microsoft.Build.Framework.dll", + "lib/net472/Microsoft.Build.Framework.pdb", + "lib/net472/Microsoft.Build.Framework.xml", + "lib/net8.0/Microsoft.Build.Framework.dll", + "lib/net8.0/Microsoft.Build.Framework.pdb", + "lib/net8.0/Microsoft.Build.Framework.xml", + "microsoft.build.framework.17.8.3.nupkg.sha512", + "microsoft.build.framework.nuspec", + "notices/THIRDPARTYNOTICES.txt", + "ref/net472/Microsoft.Build.Framework.dll", + "ref/net472/Microsoft.Build.Framework.xml", + "ref/net8.0/Microsoft.Build.Framework.dll", + "ref/net8.0/Microsoft.Build.Framework.xml", + "ref/netstandard2.0/Microsoft.Build.Framework.dll", + "ref/netstandard2.0/Microsoft.Build.Framework.xml" + ] + }, + "Microsoft.Build.Locator/1.7.8": { + "sha512": "sPy10x527Ph16S2u0yGME4S6ohBKJ69WfjeGG/bvELYeZVmJdKjxgnlL8cJJJLGV/cZIRqSfB12UDB8ICakOog==", + "type": "package", + "path": "microsoft.build.locator/1.7.8", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "MSBuild-NuGet-Icon.png", + "build/Microsoft.Build.Locator.props", + "build/Microsoft.Build.Locator.targets", + "lib/net46/Microsoft.Build.Locator.dll", + "lib/net6.0/Microsoft.Build.Locator.dll", + "microsoft.build.locator.1.7.8.nupkg.sha512", + "microsoft.build.locator.nuspec" + ] + }, + "Microsoft.CodeAnalysis.Analyzers/3.3.4": { + "sha512": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==", + "type": "package", + "path": "microsoft.codeanalysis.analyzers/3.3.4", + "hasTools": true, + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.txt", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.Analyzers.dll", + "analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.Analyzers.dll", + "analyzers/dotnet/cs/cs/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/de/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/es/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/fr/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/it/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/ja/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/ko/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/pl/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/pt-BR/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/ru/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/tr/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hans/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/cs/zh-Hant/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.Analyzers.dll", + "analyzers/dotnet/vb/Microsoft.CodeAnalysis.VisualBasic.Analyzers.dll", + "analyzers/dotnet/vb/cs/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/de/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/es/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/fr/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/it/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/ja/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/ko/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/pl/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/pt-BR/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/ru/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/tr/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hans/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "analyzers/dotnet/vb/zh-Hant/Microsoft.CodeAnalysis.Analyzers.resources.dll", + "buildTransitive/Microsoft.CodeAnalysis.Analyzers.props", + "buildTransitive/Microsoft.CodeAnalysis.Analyzers.targets", + "buildTransitive/config/analysislevel_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevel_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevel_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_all.globalconfig", + "buildTransitive/config/analysislevel_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_default.globalconfig", + "buildTransitive/config/analysislevel_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevel_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_none.globalconfig", + "buildTransitive/config/analysislevel_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevel_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_all.globalconfig", + "buildTransitive/config/analysislevel_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_default.globalconfig", + "buildTransitive/config/analysislevel_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_minimum.globalconfig", + "buildTransitive/config/analysislevel_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_none.globalconfig", + "buildTransitive/config/analysislevel_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_3_recommended.globalconfig", + "buildTransitive/config/analysislevel_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_4_3_all.globalconfig", + "buildTransitive/config/analysislevel_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_4_3_default.globalconfig", + "buildTransitive/config/analysislevel_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevel_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_4_3_none.globalconfig", + "buildTransitive/config/analysislevel_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevel_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevel_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelcorrectness_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_all.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_default.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_none.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_all.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_default.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_minimum.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_none.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_recommended.globalconfig", + "buildTransitive/config/analysislevelcorrectness_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_all.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_default.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_none.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelcorrectness_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevellibrary_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_all.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_default.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_none.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevellibrary_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_all.globalconfig", + "buildTransitive/config/analysislevellibrary_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_default.globalconfig", + "buildTransitive/config/analysislevellibrary_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_minimum.globalconfig", + "buildTransitive/config/analysislevellibrary_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_none.globalconfig", + "buildTransitive/config/analysislevellibrary_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_3_recommended.globalconfig", + "buildTransitive/config/analysislevellibrary_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_all.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_default.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_none.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevellibrary_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscompatibility_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysiscorrectness_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdesign_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisdocumentation_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysislocalization_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisperformance_4_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_2_9_8_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_3_recommended_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_all.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_all_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_default.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_default_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_minimum.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_minimum_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_none.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_none_warnaserror.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_recommended.globalconfig", + "buildTransitive/config/analysislevelmicrosoftcodeanalysisreleasetracking_4_3_recommended_warnaserror.globalconfig", + "documentation/Analyzer Configuration.md", + "documentation/Microsoft.CodeAnalysis.Analyzers.md", + "documentation/Microsoft.CodeAnalysis.Analyzers.sarif", + "editorconfig/AllRulesDefault/.editorconfig", + "editorconfig/AllRulesDisabled/.editorconfig", + "editorconfig/AllRulesEnabled/.editorconfig", + "editorconfig/CorrectnessRulesDefault/.editorconfig", + "editorconfig/CorrectnessRulesEnabled/.editorconfig", + "editorconfig/DataflowRulesDefault/.editorconfig", + "editorconfig/DataflowRulesEnabled/.editorconfig", + "editorconfig/LibraryRulesDefault/.editorconfig", + "editorconfig/LibraryRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisCompatibilityRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisCompatibilityRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisCorrectnessRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisCorrectnessRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisDesignRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisDesignRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisDocumentationRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisDocumentationRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisLocalizationRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisLocalizationRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisPerformanceRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisPerformanceRulesEnabled/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisReleaseTrackingRulesDefault/.editorconfig", + "editorconfig/MicrosoftCodeAnalysisReleaseTrackingRulesEnabled/.editorconfig", + "editorconfig/PortedFromFxCopRulesDefault/.editorconfig", + "editorconfig/PortedFromFxCopRulesEnabled/.editorconfig", + "microsoft.codeanalysis.analyzers.3.3.4.nupkg.sha512", + "microsoft.codeanalysis.analyzers.nuspec", + "rulesets/AllRulesDefault.ruleset", + "rulesets/AllRulesDisabled.ruleset", + "rulesets/AllRulesEnabled.ruleset", + "rulesets/CorrectnessRulesDefault.ruleset", + "rulesets/CorrectnessRulesEnabled.ruleset", + "rulesets/DataflowRulesDefault.ruleset", + "rulesets/DataflowRulesEnabled.ruleset", + "rulesets/LibraryRulesDefault.ruleset", + "rulesets/LibraryRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisCompatibilityRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisCompatibilityRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisCorrectnessRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisCorrectnessRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisDesignRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisDesignRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisDocumentationRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisDocumentationRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisLocalizationRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisLocalizationRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisPerformanceRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisPerformanceRulesEnabled.ruleset", + "rulesets/MicrosoftCodeAnalysisReleaseTrackingRulesDefault.ruleset", + "rulesets/MicrosoftCodeAnalysisReleaseTrackingRulesEnabled.ruleset", + "rulesets/PortedFromFxCopRulesDefault.ruleset", + "rulesets/PortedFromFxCopRulesEnabled.ruleset", + "tools/install.ps1", + "tools/uninstall.ps1" + ] + }, + "Microsoft.CodeAnalysis.Common/4.8.0": { + "sha512": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==", + "type": "package", + "path": "microsoft.codeanalysis.common/4.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.rtf", + "lib/net6.0/Microsoft.CodeAnalysis.dll", + "lib/net6.0/Microsoft.CodeAnalysis.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.xml", + "lib/net6.0/cs/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/de/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/es/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/fr/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/it/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/ja/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/ko/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/pl/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/pt-BR/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/ru/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/tr/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll", + "lib/net6.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/Microsoft.CodeAnalysis.dll", + "lib/net7.0/Microsoft.CodeAnalysis.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.xml", + "lib/net7.0/cs/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/de/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/es/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/fr/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/it/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/ja/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/ko/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/pl/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/ru/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/tr/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll", + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.pdb", + "lib/netstandard2.0/Microsoft.CodeAnalysis.xml", + "lib/netstandard2.0/cs/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/de/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/es/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/fr/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/it/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/ja/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/ko/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/pl/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/ru/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/tr/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll", + "microsoft.codeanalysis.common.4.8.0.nupkg.sha512", + "microsoft.codeanalysis.common.nuspec" + ] + }, + "Microsoft.CodeAnalysis.CSharp/4.8.0": { + "sha512": "+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==", + "type": "package", + "path": "microsoft.codeanalysis.csharp/4.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.rtf", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.dll", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.xml", + "lib/net6.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net6.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.dll", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.xml", + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.pdb", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.xml", + "lib/netstandard2.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll", + "microsoft.codeanalysis.csharp.4.8.0.nupkg.sha512", + "microsoft.codeanalysis.csharp.nuspec" + ] + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces/4.8.0": { + "sha512": "3amm4tq4Lo8/BGvg9p3BJh3S9nKq2wqCXfS7138i69TUpo/bD+XvD0hNurpEBtcNZhi1FyutiomKJqVF39ugYA==", + "type": "package", + "path": "microsoft.codeanalysis.csharp.workspaces/4.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.rtf", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.Workspaces.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.CSharp.Workspaces.xml", + "lib/net6.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net6.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.CSharp.Workspaces.xml", + "lib/net7.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.Workspaces.pdb", + "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.Workspaces.xml", + "lib/netstandard2.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll", + "microsoft.codeanalysis.csharp.workspaces.4.8.0.nupkg.sha512", + "microsoft.codeanalysis.csharp.workspaces.nuspec" + ] + }, + "Microsoft.CodeAnalysis.Workspaces.Common/4.8.0": { + "sha512": "LXyV+MJKsKRu3FGJA3OmSk40OUIa/dQCFLOnm5X8MNcujx7hzGu8o+zjXlb/cy5xUdZK2UKYb9YaQ2E8m9QehQ==", + "type": "package", + "path": "microsoft.codeanalysis.workspaces.common/4.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.rtf", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.dll", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.xml", + "lib/net6.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net6.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.dll", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.xml", + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.Workspaces.dll", + "lib/netstandard2.0/Microsoft.CodeAnalysis.Workspaces.pdb", + "lib/netstandard2.0/Microsoft.CodeAnalysis.Workspaces.xml", + "lib/netstandard2.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "lib/netstandard2.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll", + "microsoft.codeanalysis.workspaces.common.4.8.0.nupkg.sha512", + "microsoft.codeanalysis.workspaces.common.nuspec" + ] + }, + "Microsoft.CodeAnalysis.Workspaces.MSBuild/4.8.0": { + "sha512": "IEYreI82QZKklp54yPHxZNG9EKSK6nHEkeuf+0Asie9llgS1gp0V1hw7ODG+QyoB7MuAnNQHmeV1Per/ECpv6A==", + "type": "package", + "path": "microsoft.codeanalysis.workspaces.msbuild/4.8.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "ThirdPartyNotices.rtf", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.pdb", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.xml", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.pdb", + "lib/net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.xml", + "lib/net472/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net472/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.runtimeconfig.json", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.xml", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.pdb", + "lib/net6.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.xml", + "lib/net6.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net6.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.runtimeconfig.json", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.xml", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.pdb", + "lib/net7.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.xml", + "lib/net7.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "lib/net7.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll", + "microsoft.codeanalysis.workspaces.msbuild.4.8.0.nupkg.sha512", + "microsoft.codeanalysis.workspaces.msbuild.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore/9.0.4": { + "sha512": "+5IAX0aicQYCRfN4pAjad+JPwdEYoVEM3Z1Cl8/EiEv3FVHQHdd8TJQpQIslQDDQS/UsUMb0MsOXwqOh+TJtRw==", + "type": "package", + "path": "microsoft.entityframeworkcore/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "buildTransitive/net8.0/Microsoft.EntityFrameworkCore.props", + "lib/net8.0/Microsoft.EntityFrameworkCore.dll", + "lib/net8.0/Microsoft.EntityFrameworkCore.xml", + "microsoft.entityframeworkcore.9.0.4.nupkg.sha512", + "microsoft.entityframeworkcore.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Abstractions/9.0.4": { + "sha512": "E0pkWzI0liqu2ogqJ1kohk2eGkYRhf5tI75HGF6IQDARsshY/0w+prGyLvNuUeV7B8I7vYQZ4CzAKYKxw7b9gQ==", + "type": "package", + "path": "microsoft.entityframeworkcore.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll", + "lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.xml", + "microsoft.entityframeworkcore.abstractions.9.0.4.nupkg.sha512", + "microsoft.entityframeworkcore.abstractions.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Analyzers/9.0.4": { + "sha512": "cMsm1O7g9X5qbB2wjHf3BVVvGwkG+zeXQ+M91I1Bm6RfylFMImqBPzs0+vmuef7fPxr2yOzPhIfJ2wQJfmtaSw==", + "type": "package", + "path": "microsoft.entityframeworkcore.analyzers/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll", + "docs/PACKAGE.md", + "microsoft.entityframeworkcore.analyzers.9.0.4.nupkg.sha512", + "microsoft.entityframeworkcore.analyzers.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Design/9.0.4": { + "sha512": "0NdtmsbYfMr2HyF+W6L+kPaHJl1nAmFjWj0MfI5G+CFeWZxDwltQxzzwSmZQ4QhS5z8zjczGXwHZ8e3iFaoiXA==", + "type": "package", + "path": "microsoft.entityframeworkcore.design/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "build/net8.0/Microsoft.EntityFrameworkCore.Design.props", + "lib/net8.0/Microsoft.EntityFrameworkCore.Design.dll", + "lib/net8.0/Microsoft.EntityFrameworkCore.Design.xml", + "microsoft.entityframeworkcore.design.9.0.4.nupkg.sha512", + "microsoft.entityframeworkcore.design.nuspec" + ] + }, + "Microsoft.EntityFrameworkCore.Relational/9.0.4": { + "sha512": "OjJ+xh/wQff5b0wiC3SPvoQqTA2boZeJQf+15+3+OJPtjBKzvxuwr25QRIu1p1t+K8ryQ8pzaoZ7eOpXfNzVGA==", + "type": "package", + "path": "microsoft.entityframeworkcore.relational/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "PACKAGE.md", + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll", + "lib/net8.0/Microsoft.EntityFrameworkCore.Relational.xml", + "microsoft.entityframeworkcore.relational.9.0.4.nupkg.sha512", + "microsoft.entityframeworkcore.relational.nuspec" + ] + }, + "Microsoft.Extensions.Caching.Abstractions/9.0.4": { + "sha512": "imcZ5BGhBw5mNsWLepBbqqumWaFe0GtvyCvne2/2wsDIBRa2+Lhx4cU/pKt/4BwOizzUEOls2k1eOJQXHGMalg==", + "type": "package", + "path": "microsoft.extensions.caching.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Abstractions.targets", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Abstractions.xml", + "microsoft.extensions.caching.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.caching.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Caching.Memory/9.0.4": { + "sha512": "G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==", + "type": "package", + "path": "microsoft.extensions.caching.memory/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Caching.Memory.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Caching.Memory.targets", + "lib/net462/Microsoft.Extensions.Caching.Memory.dll", + "lib/net462/Microsoft.Extensions.Caching.Memory.xml", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net8.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/net9.0/Microsoft.Extensions.Caching.Memory.xml", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.dll", + "lib/netstandard2.0/Microsoft.Extensions.Caching.Memory.xml", + "microsoft.extensions.caching.memory.9.0.4.nupkg.sha512", + "microsoft.extensions.caching.memory.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Configuration.Abstractions/9.0.4": { + "sha512": "0LN/DiIKvBrkqp7gkF3qhGIeZk6/B63PthAHjQsxymJfIBcz0kbf4/p/t4lMgggVxZ+flRi5xvTwlpPOoZk8fg==", + "type": "package", + "path": "microsoft.extensions.configuration.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Configuration.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Configuration.Abstractions.targets", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.xml", + "microsoft.extensions.configuration.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.configuration.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection/9.0.4": { + "sha512": "f2MTUaS2EQ3lX4325ytPAISZqgBfXmY0WvgD80ji6Z20AoDNiCESxsqo6mFRwHJD/jfVKRw9FsW6+86gNre3ug==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.targets", + "lib/net462/Microsoft.Extensions.DependencyInjection.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.xml", + "microsoft.extensions.dependencyinjection.9.0.4.nupkg.sha512", + "microsoft.extensions.dependencyinjection.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyInjection.Abstractions/9.0.4": { + "sha512": "UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg==", + "type": "package", + "path": "microsoft.extensions.dependencyinjection.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll", + "lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml", + "microsoft.extensions.dependencyinjection.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.dependencyinjection.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.DependencyModel/9.0.4": { + "sha512": "ACtnvl3H3M/f8Z42980JxsNu7V9PPbzys4vBs83ZewnsgKd7JeYK18OMPo0g+MxAHrpgMrjmlinXDiaSRPcVnA==", + "type": "package", + "path": "microsoft.extensions.dependencymodel/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.DependencyModel.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyModel.targets", + "lib/net462/Microsoft.Extensions.DependencyModel.dll", + "lib/net462/Microsoft.Extensions.DependencyModel.xml", + "lib/net8.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net8.0/Microsoft.Extensions.DependencyModel.xml", + "lib/net9.0/Microsoft.Extensions.DependencyModel.dll", + "lib/net9.0/Microsoft.Extensions.DependencyModel.xml", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.dll", + "lib/netstandard2.0/Microsoft.Extensions.DependencyModel.xml", + "microsoft.extensions.dependencymodel.9.0.4.nupkg.sha512", + "microsoft.extensions.dependencymodel.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging/9.0.4": { + "sha512": "xW6QPYsqhbuWBO9/1oA43g/XPKbohJx+7G8FLQgQXIriYvY7s+gxr2wjQJfRoPO900dvvv2vVH7wZovG+M1m6w==", + "type": "package", + "path": "microsoft.extensions.logging/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Logging.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.targets", + "lib/net462/Microsoft.Extensions.Logging.dll", + "lib/net462/Microsoft.Extensions.Logging.xml", + "lib/net8.0/Microsoft.Extensions.Logging.dll", + "lib/net8.0/Microsoft.Extensions.Logging.xml", + "lib/net9.0/Microsoft.Extensions.Logging.dll", + "lib/net9.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.xml", + "lib/netstandard2.1/Microsoft.Extensions.Logging.dll", + "lib/netstandard2.1/Microsoft.Extensions.Logging.xml", + "microsoft.extensions.logging.9.0.4.nupkg.sha512", + "microsoft.extensions.logging.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Logging.Abstractions/9.0.4": { + "sha512": "0MXlimU4Dud6t+iNi5NEz3dO2w1HXdhoOLaYFuLPCjAsvlPQGwOT6V2KZRMLEhCAm/stSZt1AUv0XmDdkjvtbw==", + "type": "package", + "path": "microsoft.extensions.logging.abstractions/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Logging.Generators.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Logging.Generators.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net462/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.targets", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net462/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net8.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/net9.0/Microsoft.Extensions.Logging.Abstractions.xml", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll", + "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.xml", + "microsoft.extensions.logging.abstractions.9.0.4.nupkg.sha512", + "microsoft.extensions.logging.abstractions.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Options/9.0.4": { + "sha512": "fiFI2+58kicqVZyt/6obqoFwHiab7LC4FkQ3mmiBJ28Yy4fAvy2+v9MRnSvvlOO8chTOjKsdafFl/K9veCPo5g==", + "type": "package", + "path": "microsoft.extensions.options/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/Microsoft.Extensions.Options.SourceGeneration.resources.dll", + "buildTransitive/net461/Microsoft.Extensions.Options.targets", + "buildTransitive/net462/Microsoft.Extensions.Options.targets", + "buildTransitive/net8.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Options.targets", + "buildTransitive/netstandard2.0/Microsoft.Extensions.Options.targets", + "lib/net462/Microsoft.Extensions.Options.dll", + "lib/net462/Microsoft.Extensions.Options.xml", + "lib/net8.0/Microsoft.Extensions.Options.dll", + "lib/net8.0/Microsoft.Extensions.Options.xml", + "lib/net9.0/Microsoft.Extensions.Options.dll", + "lib/net9.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.0/Microsoft.Extensions.Options.dll", + "lib/netstandard2.0/Microsoft.Extensions.Options.xml", + "lib/netstandard2.1/Microsoft.Extensions.Options.dll", + "lib/netstandard2.1/Microsoft.Extensions.Options.xml", + "microsoft.extensions.options.9.0.4.nupkg.sha512", + "microsoft.extensions.options.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Microsoft.Extensions.Primitives/9.0.4": { + "sha512": "SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA==", + "type": "package", + "path": "microsoft.extensions.primitives/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/Microsoft.Extensions.Primitives.targets", + "buildTransitive/net462/_._", + "buildTransitive/net8.0/_._", + "buildTransitive/netcoreapp2.0/Microsoft.Extensions.Primitives.targets", + "lib/net462/Microsoft.Extensions.Primitives.dll", + "lib/net462/Microsoft.Extensions.Primitives.xml", + "lib/net8.0/Microsoft.Extensions.Primitives.dll", + "lib/net8.0/Microsoft.Extensions.Primitives.xml", + "lib/net9.0/Microsoft.Extensions.Primitives.dll", + "lib/net9.0/Microsoft.Extensions.Primitives.xml", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll", + "lib/netstandard2.0/Microsoft.Extensions.Primitives.xml", + "microsoft.extensions.primitives.9.0.4.nupkg.sha512", + "microsoft.extensions.primitives.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "Mono.TextTemplating/3.0.0": { + "sha512": "YqueG52R/Xej4VVbKuRIodjiAhV0HR/XVbLbNrJhCZnzjnSjgMJ/dCdV0akQQxavX6hp/LC6rqLGLcXeQYU7XA==", + "type": "package", + "path": "mono.texttemplating/3.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.txt/LICENSE", + "buildTransitive/Mono.TextTemplating.targets", + "lib/net472/Mono.TextTemplating.dll", + "lib/net6.0/Mono.TextTemplating.dll", + "lib/netstandard2.0/Mono.TextTemplating.dll", + "mono.texttemplating.3.0.0.nupkg.sha512", + "mono.texttemplating.nuspec", + "readme.md" + ] + }, + "Npgsql/9.0.3": { + "sha512": "tPvY61CxOAWxNsKLEBg+oR646X4Bc8UmyQ/tJszL/7mEmIXQnnBhVJZrZEEUv0Bstu0mEsHZD5At3EO8zQRAYw==", + "type": "package", + "path": "npgsql/9.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net6.0/Npgsql.dll", + "lib/net6.0/Npgsql.xml", + "lib/net8.0/Npgsql.dll", + "lib/net8.0/Npgsql.xml", + "npgsql.9.0.3.nupkg.sha512", + "npgsql.nuspec", + "postgresql.png" + ] + }, + "Npgsql.EntityFrameworkCore.PostgreSQL/9.0.4": { + "sha512": "mw5vcY2IEc7L+IeGrxpp/J5OSnCcjkjAgJYCm/eD52wpZze8zsSifdqV7zXslSMmfJG2iIUGZyo3KuDtEFKwMQ==", + "type": "package", + "path": "npgsql.entityframeworkcore.postgresql/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "README.md", + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll", + "lib/net8.0/Npgsql.EntityFrameworkCore.PostgreSQL.xml", + "npgsql.entityframeworkcore.postgresql.9.0.4.nupkg.sha512", + "npgsql.entityframeworkcore.postgresql.nuspec", + "postgresql.png" + ] + }, + "System.CodeDom/6.0.0": { + "sha512": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==", + "type": "package", + "path": "system.codedom/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/netcoreapp2.0/System.CodeDom.targets", + "buildTransitive/netcoreapp3.1/_._", + "lib/net461/System.CodeDom.dll", + "lib/net461/System.CodeDom.xml", + "lib/net6.0/System.CodeDom.dll", + "lib/net6.0/System.CodeDom.xml", + "lib/netstandard2.0/System.CodeDom.dll", + "lib/netstandard2.0/System.CodeDom.xml", + "system.codedom.6.0.0.nupkg.sha512", + "system.codedom.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Collections.Immutable/7.0.0": { + "sha512": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==", + "type": "package", + "path": "system.collections.immutable/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "README.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Collections.Immutable.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Collections.Immutable.targets", + "lib/net462/System.Collections.Immutable.dll", + "lib/net462/System.Collections.Immutable.xml", + "lib/net6.0/System.Collections.Immutable.dll", + "lib/net6.0/System.Collections.Immutable.xml", + "lib/net7.0/System.Collections.Immutable.dll", + "lib/net7.0/System.Collections.Immutable.xml", + "lib/netstandard2.0/System.Collections.Immutable.dll", + "lib/netstandard2.0/System.Collections.Immutable.xml", + "system.collections.immutable.7.0.0.nupkg.sha512", + "system.collections.immutable.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition/7.0.0": { + "sha512": "tRwgcAkDd85O8Aq6zHDANzQaq380cek9lbMg5Qma46u5BZXq/G+XvIYmu+UI+BIIZ9zssXLYrkTykEqxxvhcmg==", + "type": "package", + "path": "system.composition/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.targets", + "lib/net461/_._", + "lib/netcoreapp2.0/_._", + "lib/netstandard2.0/_._", + "system.composition.7.0.0.nupkg.sha512", + "system.composition.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition.AttributedModel/7.0.0": { + "sha512": "2QzClqjElKxgI1jK1Jztnq44/8DmSuTSGGahXqQ4TdEV0h9s2KikQZIgcEqVzR7OuWDFPGLHIprBJGQEPr8fAQ==", + "type": "package", + "path": "system.composition.attributedmodel/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.AttributedModel.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.AttributedModel.targets", + "lib/net462/System.Composition.AttributedModel.dll", + "lib/net462/System.Composition.AttributedModel.xml", + "lib/net6.0/System.Composition.AttributedModel.dll", + "lib/net6.0/System.Composition.AttributedModel.xml", + "lib/net7.0/System.Composition.AttributedModel.dll", + "lib/net7.0/System.Composition.AttributedModel.xml", + "lib/netstandard2.0/System.Composition.AttributedModel.dll", + "lib/netstandard2.0/System.Composition.AttributedModel.xml", + "system.composition.attributedmodel.7.0.0.nupkg.sha512", + "system.composition.attributedmodel.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition.Convention/7.0.0": { + "sha512": "IMhTlpCs4HmlD8B+J8/kWfwX7vrBBOs6xyjSTzBlYSs7W4OET4tlkR/Sg9NG8jkdJH9Mymq0qGdYS1VPqRTBnQ==", + "type": "package", + "path": "system.composition.convention/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.Convention.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.Convention.targets", + "lib/net462/System.Composition.Convention.dll", + "lib/net462/System.Composition.Convention.xml", + "lib/net6.0/System.Composition.Convention.dll", + "lib/net6.0/System.Composition.Convention.xml", + "lib/net7.0/System.Composition.Convention.dll", + "lib/net7.0/System.Composition.Convention.xml", + "lib/netstandard2.0/System.Composition.Convention.dll", + "lib/netstandard2.0/System.Composition.Convention.xml", + "system.composition.convention.7.0.0.nupkg.sha512", + "system.composition.convention.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition.Hosting/7.0.0": { + "sha512": "eB6gwN9S+54jCTBJ5bpwMOVerKeUfGGTYCzz3QgDr1P55Gg/Wb27ShfPIhLMjmZ3MoAKu8uUSv6fcCdYJTN7Bg==", + "type": "package", + "path": "system.composition.hosting/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.Hosting.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.Hosting.targets", + "lib/net462/System.Composition.Hosting.dll", + "lib/net462/System.Composition.Hosting.xml", + "lib/net6.0/System.Composition.Hosting.dll", + "lib/net6.0/System.Composition.Hosting.xml", + "lib/net7.0/System.Composition.Hosting.dll", + "lib/net7.0/System.Composition.Hosting.xml", + "lib/netstandard2.0/System.Composition.Hosting.dll", + "lib/netstandard2.0/System.Composition.Hosting.xml", + "system.composition.hosting.7.0.0.nupkg.sha512", + "system.composition.hosting.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition.Runtime/7.0.0": { + "sha512": "aZJ1Zr5Txe925rbo4742XifEyW0MIni1eiUebmcrP3HwLXZ3IbXUj4MFMUH/RmnJOAQiS401leg/2Sz1MkApDw==", + "type": "package", + "path": "system.composition.runtime/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.Runtime.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.Runtime.targets", + "lib/net462/System.Composition.Runtime.dll", + "lib/net462/System.Composition.Runtime.xml", + "lib/net6.0/System.Composition.Runtime.dll", + "lib/net6.0/System.Composition.Runtime.xml", + "lib/net7.0/System.Composition.Runtime.dll", + "lib/net7.0/System.Composition.Runtime.xml", + "lib/netstandard2.0/System.Composition.Runtime.dll", + "lib/netstandard2.0/System.Composition.Runtime.xml", + "system.composition.runtime.7.0.0.nupkg.sha512", + "system.composition.runtime.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Composition.TypedParts/7.0.0": { + "sha512": "ZK0KNPfbtxVceTwh+oHNGUOYV2WNOHReX2AXipuvkURC7s/jPwoWfsu3SnDBDgofqbiWr96geofdQ2erm/KTHg==", + "type": "package", + "path": "system.composition.typedparts/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Composition.TypedParts.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Composition.TypedParts.targets", + "lib/net462/System.Composition.TypedParts.dll", + "lib/net462/System.Composition.TypedParts.xml", + "lib/net6.0/System.Composition.TypedParts.dll", + "lib/net6.0/System.Composition.TypedParts.xml", + "lib/net7.0/System.Composition.TypedParts.dll", + "lib/net7.0/System.Composition.TypedParts.xml", + "lib/netstandard2.0/System.Composition.TypedParts.dll", + "lib/netstandard2.0/System.Composition.TypedParts.xml", + "system.composition.typedparts.7.0.0.nupkg.sha512", + "system.composition.typedparts.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.IO.Pipelines/7.0.0": { + "sha512": "jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg==", + "type": "package", + "path": "system.io.pipelines/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.IO.Pipelines.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.IO.Pipelines.targets", + "lib/net462/System.IO.Pipelines.dll", + "lib/net462/System.IO.Pipelines.xml", + "lib/net6.0/System.IO.Pipelines.dll", + "lib/net6.0/System.IO.Pipelines.xml", + "lib/net7.0/System.IO.Pipelines.dll", + "lib/net7.0/System.IO.Pipelines.xml", + "lib/netstandard2.0/System.IO.Pipelines.dll", + "lib/netstandard2.0/System.IO.Pipelines.xml", + "system.io.pipelines.7.0.0.nupkg.sha512", + "system.io.pipelines.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Reflection.Metadata/7.0.0": { + "sha512": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==", + "type": "package", + "path": "system.reflection.metadata/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "README.md", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Reflection.Metadata.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Reflection.Metadata.targets", + "lib/net462/System.Reflection.Metadata.dll", + "lib/net462/System.Reflection.Metadata.xml", + "lib/net6.0/System.Reflection.Metadata.dll", + "lib/net6.0/System.Reflection.Metadata.xml", + "lib/net7.0/System.Reflection.Metadata.dll", + "lib/net7.0/System.Reflection.Metadata.xml", + "lib/netstandard2.0/System.Reflection.Metadata.dll", + "lib/netstandard2.0/System.Reflection.Metadata.xml", + "system.reflection.metadata.7.0.0.nupkg.sha512", + "system.reflection.metadata.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "sha512": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + "type": "package", + "path": "system.runtime.compilerservices.unsafe/6.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/netcoreapp2.0/System.Runtime.CompilerServices.Unsafe.targets", + "buildTransitive/netcoreapp3.1/_._", + "lib/net461/System.Runtime.CompilerServices.Unsafe.dll", + "lib/net461/System.Runtime.CompilerServices.Unsafe.xml", + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.dll", + "lib/net6.0/System.Runtime.CompilerServices.Unsafe.xml", + "lib/netcoreapp3.1/System.Runtime.CompilerServices.Unsafe.dll", + "lib/netcoreapp3.1/System.Runtime.CompilerServices.Unsafe.xml", + "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll", + "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.xml", + "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512", + "system.runtime.compilerservices.unsafe.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Text.Json/9.0.4": { + "sha512": "pYtmpcO6R3Ef1XilZEHgXP2xBPVORbYEzRP7dl0IAAbN8Dm+kfwio8aCKle97rAWXOExr292MuxWYurIuwN62g==", + "type": "package", + "path": "system.text.json/9.0.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "PACKAGE.md", + "THIRD-PARTY-NOTICES.TXT", + "analyzers/dotnet/roslyn3.11/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn3.11/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn3.11/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.0/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.0/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/System.Text.Json.SourceGeneration.dll", + "analyzers/dotnet/roslyn4.4/cs/cs/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/de/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/es/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/fr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/it/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ja/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ko/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pl/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/pt-BR/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/ru/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/tr/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hans/System.Text.Json.SourceGeneration.resources.dll", + "analyzers/dotnet/roslyn4.4/cs/zh-Hant/System.Text.Json.SourceGeneration.resources.dll", + "buildTransitive/net461/System.Text.Json.targets", + "buildTransitive/net462/System.Text.Json.targets", + "buildTransitive/net8.0/System.Text.Json.targets", + "buildTransitive/netcoreapp2.0/System.Text.Json.targets", + "buildTransitive/netstandard2.0/System.Text.Json.targets", + "lib/net462/System.Text.Json.dll", + "lib/net462/System.Text.Json.xml", + "lib/net8.0/System.Text.Json.dll", + "lib/net8.0/System.Text.Json.xml", + "lib/net9.0/System.Text.Json.dll", + "lib/net9.0/System.Text.Json.xml", + "lib/netstandard2.0/System.Text.Json.dll", + "lib/netstandard2.0/System.Text.Json.xml", + "system.text.json.9.0.4.nupkg.sha512", + "system.text.json.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "System.Threading.Channels/7.0.0": { + "sha512": "qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA==", + "type": "package", + "path": "system.threading.channels/7.0.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "Icon.png", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "buildTransitive/net461/System.Threading.Channels.targets", + "buildTransitive/net462/_._", + "buildTransitive/net6.0/_._", + "buildTransitive/netcoreapp2.0/System.Threading.Channels.targets", + "lib/net462/System.Threading.Channels.dll", + "lib/net462/System.Threading.Channels.xml", + "lib/net6.0/System.Threading.Channels.dll", + "lib/net6.0/System.Threading.Channels.xml", + "lib/net7.0/System.Threading.Channels.dll", + "lib/net7.0/System.Threading.Channels.xml", + "lib/netstandard2.0/System.Threading.Channels.dll", + "lib/netstandard2.0/System.Threading.Channels.xml", + "lib/netstandard2.1/System.Threading.Channels.dll", + "lib/netstandard2.1/System.Threading.Channels.xml", + "system.threading.channels.7.0.0.nupkg.sha512", + "system.threading.channels.nuspec", + "useSharedDesignerContext.txt" + ] + }, + "PleasePayMe.Application/1.0.0": { + "type": "project", + "path": "../PleasePayMe.Application/PleasePayMe.Application.csproj", + "msbuildProject": "../PleasePayMe.Application/PleasePayMe.Application.csproj" + }, + "PleasePayMe.Domain/1.0.0": { + "type": "project", + "path": "../PleasePayMe.Domain/PleasePayMe.Domain.csproj", + "msbuildProject": "../PleasePayMe.Domain/PleasePayMe.Domain.csproj" + } + }, + "projectFileDependencyGroups": { + "net9.0": [ + "Microsoft.EntityFrameworkCore.Design >= 9.0.4", + "Npgsql.EntityFrameworkCore.PostgreSQL >= 9.0.4", + "PleasePayMe.Application >= 1.0.0", + "PleasePayMe.Domain >= 1.0.0" + ] + }, + "packageFolders": { + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj", + "projectName": "PleasePayMe.Infrastructure", + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj", + "packagesPath": "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget", + "outputPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\ggpo1\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net9.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "projectReferences": { + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Application\\PleasePayMe.Application.csproj" + }, + "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj": { + "projectPath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Domain\\PleasePayMe.Domain.csproj" + } + } + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + }, + "SdkAnalysisLevel": "9.0.300" + }, + "frameworks": { + "net9.0": { + "targetAlias": "net9.0", + "dependencies": { + "Microsoft.EntityFrameworkCore.Design": { + "include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive", + "suppressParent": "All", + "target": "Package", + "version": "[9.0.4, )" + }, + "Npgsql.EntityFrameworkCore.PostgreSQL": { + "target": "Package", + "version": "[9.0.4, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.315/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/src/PleasePayMe.Infrastructure/obj/project.nuget.cache b/src/PleasePayMe.Infrastructure/obj/project.nuget.cache new file mode 100644 index 0000000..8b9149f --- /dev/null +++ b/src/PleasePayMe.Infrastructure/obj/project.nuget.cache @@ -0,0 +1,50 @@ +{ + "version": 2, + "dgSpecHash": "H7oXDMtIpbM=", + "success": true, + "projectFilePath": "c:\\Users\\ggpo1\\Desktop\\please_pay_me_bot\\src\\PleasePayMe.Infrastructure\\PleasePayMe.Infrastructure.csproj", + "expectedPackageFiles": [ + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\humanizer.core\\2.14.1\\humanizer.core.2.14.1.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.bcl.asyncinterfaces\\7.0.0\\microsoft.bcl.asyncinterfaces.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.build.framework\\17.8.3\\microsoft.build.framework.17.8.3.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.build.locator\\1.7.8\\microsoft.build.locator.1.7.8.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.analyzers\\3.3.4\\microsoft.codeanalysis.analyzers.3.3.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.common\\4.8.0\\microsoft.codeanalysis.common.4.8.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.csharp\\4.8.0\\microsoft.codeanalysis.csharp.4.8.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.csharp.workspaces\\4.8.0\\microsoft.codeanalysis.csharp.workspaces.4.8.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.workspaces.common\\4.8.0\\microsoft.codeanalysis.workspaces.common.4.8.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.codeanalysis.workspaces.msbuild\\4.8.0\\microsoft.codeanalysis.workspaces.msbuild.4.8.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.entityframeworkcore\\9.0.4\\microsoft.entityframeworkcore.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.entityframeworkcore.abstractions\\9.0.4\\microsoft.entityframeworkcore.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.entityframeworkcore.analyzers\\9.0.4\\microsoft.entityframeworkcore.analyzers.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.entityframeworkcore.design\\9.0.4\\microsoft.entityframeworkcore.design.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.entityframeworkcore.relational\\9.0.4\\microsoft.entityframeworkcore.relational.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.caching.abstractions\\9.0.4\\microsoft.extensions.caching.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.caching.memory\\9.0.4\\microsoft.extensions.caching.memory.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.configuration.abstractions\\9.0.4\\microsoft.extensions.configuration.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.dependencyinjection\\9.0.4\\microsoft.extensions.dependencyinjection.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.dependencyinjection.abstractions\\9.0.4\\microsoft.extensions.dependencyinjection.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.dependencymodel\\9.0.4\\microsoft.extensions.dependencymodel.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.logging\\9.0.4\\microsoft.extensions.logging.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.logging.abstractions\\9.0.4\\microsoft.extensions.logging.abstractions.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.options\\9.0.4\\microsoft.extensions.options.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\microsoft.extensions.primitives\\9.0.4\\microsoft.extensions.primitives.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\mono.texttemplating\\3.0.0\\mono.texttemplating.3.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\npgsql\\9.0.3\\npgsql.9.0.3.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\npgsql.entityframeworkcore.postgresql\\9.0.4\\npgsql.entityframeworkcore.postgresql.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.codedom\\6.0.0\\system.codedom.6.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.collections.immutable\\7.0.0\\system.collections.immutable.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition\\7.0.0\\system.composition.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition.attributedmodel\\7.0.0\\system.composition.attributedmodel.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition.convention\\7.0.0\\system.composition.convention.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition.hosting\\7.0.0\\system.composition.hosting.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition.runtime\\7.0.0\\system.composition.runtime.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.composition.typedparts\\7.0.0\\system.composition.typedparts.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.io.pipelines\\7.0.0\\system.io.pipelines.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.reflection.metadata\\7.0.0\\system.reflection.metadata.7.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.runtime.compilerservices.unsafe\\6.0.0\\system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.text.json\\9.0.4\\system.text.json.9.0.4.nupkg.sha512", + "C:\\Users\\ggpo1\\AppData\\Local\\Temp\\cursor-sandbox-cache\\6665f3d9344be5b329c3ede124b2a60a\\nuget\\system.threading.channels\\7.0.0\\system.threading.channels.7.0.0.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..c07935d --- /dev/null +++ b/web/index.html @@ -0,0 +1,20 @@ + + + + + + Дожить до ЗП + + + + + + + +
+ + + diff --git a/web/nginx.conf b/web/nginx.conf new file mode 100644 index 0000000..2cc397b --- /dev/null +++ b/web/nginx.conf @@ -0,0 +1,44 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Важно: /api выше location / иначе try_files отдаст index.html + location /api/ { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_connect_timeout 5s; + proxy_read_timeout 60s; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-API-Token $http_x_api_token; + proxy_set_header Authorization $http_authorization; + proxy_pass_request_headers on; + } + + # без trailing slash тоже (на всякий случай) + location = /api { + return 301 /api/; + } + + location = /404.html { + root /usr/share/nginx/html; + } + + location = /502.html { + root /usr/share/nginx/html; + } + + location ~* \.apk$ { + default_type application/vnd.android.package-archive; + add_header Content-Disposition "attachment"; + try_files $uri =404; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..5d3c425 --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,1899 @@ +{ + "name": "please-pay-me-web", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "please-pay-me-web", + "version": "1.0.0", + "dependencies": { + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-router-dom": "^7.8.2" + }, + "devDependencies": { + "@types/react": "^19.1.12", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.2", + "typescript": "^5.9.2", + "vite": "^7.1.5" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.2.tgz", + "integrity": "sha512-Xa6RDoWa+hNiX6PgsljlH6W75RaONx3y6PVlbLhkEWW+GaPQ3dP5gwbL/erAzQHWwkvW5UxdD5l87Qx2FAQ/4A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.2.tgz", + "integrity": "sha512-vNASxsghMfQ5s+v3PrpnJd+ryL/26lxCCaGI+sDJ7VzmHiYXIrrVltsDhaawxLM1WcoMU2oYlbPHLaYQtBzhcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.2.tgz", + "integrity": "sha512-0dWDjmlrpZAgjPD/aPzUDhBW8APLRjAni5bOrM76wiiZm+E+KTMVKNhAzaTBohz8UyO2fKNAl0+fygbe2HZXOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.2.tgz", + "integrity": "sha512-N58uktcwzk3+qT4KHEuNdIxX1N01RWrkfVoml69EAbSaNDL+sbNVLx2RMl4Qd23lpA0fgPvyh5hHb4weD5WKmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.2.tgz", + "integrity": "sha512-HWF2zH8EAp2scWRpt2PGe6iUGz7zi04waXsdRr3zb4DWCk2ImIo5FZu0jjmD53nP/DGSvnW0e7/1ToCNZs2lZw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.2.tgz", + "integrity": "sha512-MkvcwHMnzPSMOQEwB6wHnLzmc+hT8BGc5bW/Mhmjjgx3wbj6VBnlc47XsK74kD0K9MikFfXpQqyz4NUXaUW62A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.2.tgz", + "integrity": "sha512-xe1bCKPJaKsD0tfd7Rb6bGfUogJTpKbTEEthsfdb7hTfTRNJVQTdirabQx0o6ERVba/smkM720soMY+0QnrlSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.2.tgz", + "integrity": "sha512-yOM7LdK0p6gk6+Q773OEwtlsikT1TL3yMmYsTtRlDRPha5vV2DC5x7LqRWDr6f3cSYNMKVqxzffXv8ivxNBIFQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.2.tgz", + "integrity": "sha512-qiWuJJV3DybA2IfzvRimeKXGrGuVPv1zobSY/26KnP3HbV0VcNb3ECzgvtbvF3xjSMkcooou6HASXZuLdjnhpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.2.tgz", + "integrity": "sha512-akcZquRzCY/KpUoZAMBhGf7oi4LmXq1BzRA5CPAC3rkUf28Y/sAYV3jSL+JKd7cwEyFvR5G0XVZ0gaMedP+60A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.2.tgz", + "integrity": "sha512-fNwYHrPyYyxauPzX/cpYw8Z7LQpp+DGA0KCoswA0aVFBpmdMil9XgjB8V3Ny64Ihu797+GKcuJqnsOKEmor7fA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.2.tgz", + "integrity": "sha512-XfvsgzR7DZqREdst7K1Mj3ilSUM5xLAHJcIMDFPKdxTs9q5VHOT8aMA+a683fqBu7DQl8+Sd9HCsQYL8EMY9qA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.2.tgz", + "integrity": "sha512-Pp7gVZggEFlbcuztay+/U0gVG9S1XAh8i7I1Re/htbAzo43P5wHZHw6pTyzotISqlKohoh9RpIfnOz3RbemK1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.2.tgz", + "integrity": "sha512-zkgL2xff6i7u5hau/m6FGeS8gRkLEdgLw522WGmdWWlLd9btmNl3S80mcEjtGq+kvgUekQ3+BOYLLLcPlS2LIA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.2.tgz", + "integrity": "sha512-qOheJomrkVCbbHFJ7L3J97cnhfogKqguAQphv26+3ZsAQIF1L19b+dArl//s8rjJHJLz9byykyM8NBP4nmSa1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.2.tgz", + "integrity": "sha512-XlxLD54wQhH3FciCgMofxBw27NzUe818gJH410qWvc41UT0ZFcgxVjyX5/EK8MPTupjeVWqN5oy+9pCA9mqfCA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.2.tgz", + "integrity": "sha512-vdryWeRb2bLJZf0Fv/W8se6nvsHe2PkTCxV0meheK3nQE+G90VCJcke51Miy1yQRsfm2uqIyjXOu4wmUzbTtkQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.2.tgz", + "integrity": "sha512-bcq2h2pkKmH2po4cZV8VWzO4lL40STyu/nLoFpYMQp9C2tCVNTdcVv86MwSsn3D5s1FBe2Ty1atqvVAUTMimNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.2.tgz", + "integrity": "sha512-EGoo5DMVMRkTId8fuTDaoxVlR5ZTsKULUezRjd9gCw5eeY+DjCvDpZAOlNUvKPGX+7rS1RWx6j+yOpNPx0cUgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.2.tgz", + "integrity": "sha512-MErl12k7BFHZG1TI9QF/3lSSZARzq9KgNy/FjnqFMCkv+N4RSSzoUCA5h2mqHX4Mox3WaTVKblyzhQ1zRb2ZuQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.2.tgz", + "integrity": "sha512-ILs8k07Wh4p0PsNY4wYLEaXZKMOpVhrG5QDB0yHhGhuzOfDlnyHN6sflL4El/MpUP1y8uY2lUZrv4oBS6pTT3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.2.tgz", + "integrity": "sha512-hKgB3nz/TKD3Wv78XEsyXzQsNjvhOHmwKQTvXADGOyU/cIClZDO7DsoggbdmJDPGp5V80tA3Vfv61PaKTLH3LA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.2.tgz", + "integrity": "sha512-T4wf1mudIDxN8Q/CWIBJC1u5gQUc+r5mPvlwoSbIvNkyVTP2TAFeobEmst5AQ4gMyAz4sSByVdoTDfvTmGK/8g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.2.tgz", + "integrity": "sha512-tC3IY7qoaD9Ll3/8WJQn49j5V2f/NuI9S41NOE2iM5MPs3sPIvOkVToLcz/7Bz4pyF7PSvrtwu8I/pUrGOSecQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.2.tgz", + "integrity": "sha512-6NHnk/K3eq2ZFYcU1X8g67s9qIJRCOTT92gwLMVBp08dB2uuuwI1/Q/empzL2Bfr2f2WRLJVwpp90RmacQyFkw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.23", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.23.tgz", + "integrity": "sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.427", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz", + "integrity": "sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.3.tgz", + "integrity": "sha512-gyXgtdr5uACJ5b1Q4udzjVV+tb/rlHIMJKuJ0e89R4Kzgz47z/rgP0dIKxktqIEUhDHluGTPJJH/wRha7CyqsA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.3.tgz", + "integrity": "sha512-ytVbyBBM7vMfRCam25r0WMhSVSom909A8p+8m0/f1w853dz/xfFu6etAT2SEbVoSnI+ZoPRDqIsQXVT89gp7kg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rollup": { + "version": "4.63.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.2.tgz", + "integrity": "sha512-l5eyksV4tPBj6lJyEa37YzIOCSOV7lkZzEHUdpjWZbtD7wTcFYmEYXSgm5bT4vV+dZLb9rBG1W9GROOG4NS4Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.2", + "@rollup/rollup-android-arm64": "4.63.2", + "@rollup/rollup-darwin-arm64": "4.63.2", + "@rollup/rollup-darwin-x64": "4.63.2", + "@rollup/rollup-freebsd-arm64": "4.63.2", + "@rollup/rollup-freebsd-x64": "4.63.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.2", + "@rollup/rollup-linux-arm-musleabihf": "4.63.2", + "@rollup/rollup-linux-arm64-gnu": "4.63.2", + "@rollup/rollup-linux-arm64-musl": "4.63.2", + "@rollup/rollup-linux-loong64-gnu": "4.63.2", + "@rollup/rollup-linux-loong64-musl": "4.63.2", + "@rollup/rollup-linux-ppc64-gnu": "4.63.2", + "@rollup/rollup-linux-ppc64-musl": "4.63.2", + "@rollup/rollup-linux-riscv64-gnu": "4.63.2", + "@rollup/rollup-linux-riscv64-musl": "4.63.2", + "@rollup/rollup-linux-s390x-gnu": "4.63.2", + "@rollup/rollup-linux-x64-gnu": "4.63.2", + "@rollup/rollup-linux-x64-musl": "4.63.2", + "@rollup/rollup-openbsd-x64": "4.63.2", + "@rollup/rollup-openharmony-arm64": "4.63.2", + "@rollup/rollup-win32-arm64-msvc": "4.63.2", + "@rollup/rollup-win32-ia32-msvc": "4.63.2", + "@rollup/rollup-win32-x64-gnu": "4.63.2", + "@rollup/rollup-win32-x64-msvc": "4.63.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "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": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..50cce98 --- /dev/null +++ b/web/package.json @@ -0,0 +1,24 @@ +{ + "name": "please-pay-me-web", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "typecheck": "tsc -p tsconfig.app.json --noEmit", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-router-dom": "^7.8.2" + }, + "devDependencies": { + "@types/react": "^19.1.12", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.2", + "typescript": "^5.9.2", + "vite": "^7.1.5" + } +} diff --git a/web/public/404.html b/web/public/404.html new file mode 100644 index 0000000..5c58d45 --- /dev/null +++ b/web/public/404.html @@ -0,0 +1,263 @@ + + + + + + 404 — Дожить до ЗП + + + + + + +
+ + + + + +

Дожить до ЗП

+

404

+

Страница ушла не до зарплаты

+

+ Такого адреса нет. Вернись в кабинет — бюджеты и траты на месте, просто маршрут сбился. +

+ + + +

ошибка · страница не найдена

+
+ + diff --git a/web/public/502.html b/web/public/502.html new file mode 100644 index 0000000..c954825 --- /dev/null +++ b/web/public/502.html @@ -0,0 +1,262 @@ + + + + + + 502 — Дожить до ЗП + + + + + + +
+ + + + + +

502

+

Сервер не отвечает

+

+ Шлюз не смог достучаться до кабинета. Обычно это на минуту — обновите страницу. +

+ + + +

ошибка · плохой шлюз

+
+ + diff --git a/web/public/app-icon.png b/web/public/app-icon.png new file mode 100644 index 0000000..35bbf19 Binary files /dev/null and b/web/public/app-icon.png differ diff --git a/web/public/apple-touch-icon.png b/web/public/apple-touch-icon.png new file mode 100644 index 0000000..35bbf19 Binary files /dev/null and b/web/public/apple-touch-icon.png differ diff --git a/web/src/App.tsx b/web/src/App.tsx new file mode 100644 index 0000000..fbcc98f --- /dev/null +++ b/web/src/App.tsx @@ -0,0 +1,42 @@ +import { Navigate, Route, Routes } from "react-router-dom"; +import { AuthProvider } from "./auth/AuthContext"; +import { RequireAuth } from "./auth/RequireAuth"; +import { TelegramLinkBridge } from "./auth/TelegramLinkBridge"; +import { CabinetLayout } from "./cabinet/CabinetLayout"; +import { CookieBanner } from "./legal/CookieBanner"; +import { LegalPage } from "./legal/LegalPage"; +import { LoginPage } from "./pages/LoginPage"; +import { OverviewPage } from "./pages/OverviewPage"; +import { BudgetsPage } from "./pages/BudgetsPage"; +import { OperationsPage } from "./pages/OperationsPage"; +import { JournalPage } from "./pages/JournalPage"; +import { PeriodPage } from "./pages/PeriodPage"; +import { WorkPage } from "./pages/WorkPage"; +import { CalendarPage } from "./pages/CalendarPage"; + +export default function App() { + return ( + + + + } /> + } /> + + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + } /> + + + + ); +} diff --git a/web/src/TelegramLoginButton.tsx b/web/src/TelegramLoginButton.tsx new file mode 100644 index 0000000..a197b4d --- /dev/null +++ b/web/src/TelegramLoginButton.tsx @@ -0,0 +1,59 @@ +import { useEffect, useRef } from "react"; +import { isNativeAppWebView } from "./auth/telegramRedirect"; +import type { TelegramLoginPayload } from "./types"; +import { TELEGRAM_BOT_USERNAME } from "./api"; + +declare global { + interface Window { + onTelegramAuth?: (user: TelegramLoginPayload) => void; + } +} + +type Props = { + onAuth: (user: TelegramLoginPayload) => void; +}; + +export function TelegramLoginButton({ onAuth }: Props) { + const containerRef = useRef(null); + + useEffect(() => { + if (!TELEGRAM_BOT_USERNAME || !containerRef.current) return; + + window.onTelegramAuth = (user) => { + onAuth(user); + }; + + const script = document.createElement("script"); + script.src = "https://telegram.org/js/telegram-widget.js?22"; + script.async = true; + script.setAttribute("data-telegram-login", TELEGRAM_BOT_USERNAME); + script.setAttribute("data-size", "large"); + script.setAttribute("data-radius", "10"); + script.setAttribute("data-request-access", "write"); + + // In the mobile WebView popups / iframe callbacks are unreliable. + // Redirect mode brings the signed payload back as query params on /login. + if (isNativeAppWebView()) { + script.setAttribute("data-auth-url", `${window.location.origin}/login`); + } else { + script.setAttribute("data-onauth", "onTelegramAuth(user)"); + } + + containerRef.current.innerHTML = ""; + containerRef.current.appendChild(script); + + return () => { + delete window.onTelegramAuth; + }; + }, [onAuth]); + + if (!TELEGRAM_BOT_USERNAME) { + return ( +

+ Не задан VITE_TELEGRAM_BOT_USERNAME — кнопка входа недоступна. +

+ ); + } + + return
; +} diff --git a/web/src/YandexLoginButton.tsx b/web/src/YandexLoginButton.tsx new file mode 100644 index 0000000..98edf70 --- /dev/null +++ b/web/src/YandexLoginButton.tsx @@ -0,0 +1,38 @@ +import { yandexAuthorizeUrl } from "./auth/yandexRedirect"; + +type Props = { + clientId: string; + redirectUri: string; + state?: string | null; + disabled?: boolean; +}; + +export function YandexLoginButton({ + clientId, + redirectUri, + state, + disabled = false, +}: Props) { + const href = yandexAuthorizeUrl(clientId, redirectUri, state); + const className = `app-btn app-btn--large app-btn--expanded btn--yandex${disabled ? " is-disabled" : ""}`; + + if (disabled) { + return ( + + + Войти через Яндекс + + ); + } + + return ( + + + Войти через Яндекс + + ); +} diff --git a/web/src/api.ts b/web/src/api.ts new file mode 100644 index 0000000..2b3cda9 --- /dev/null +++ b/web/src/api.ts @@ -0,0 +1,350 @@ +import { postTokenToNativeApp } from "./auth/telegramRedirect"; +import type { + AuthProviders, + AuthSession, + AuthUser, + BudgetStatus, + BudgetsList, + ExpensesPage, + ExpensesRange, + Job, + JobsList, + TelegramLoginPayload, +} from "./types"; + +export const SESSION_KEY = "ppm_session_jwt"; +const USER_KEY = "ppm_session_user"; + +const API_BASE = ( + (import.meta.env.VITE_API_BASE_URL as string | undefined) ?? "" +).replace(/\/$/, ""); + +export const TELEGRAM_BOT_USERNAME = ( + import.meta.env.VITE_TELEGRAM_BOT_USERNAME as string | undefined +)?.replace(/^@/, "") ?? ""; + +export function getAccessToken(): string { + return localStorage.getItem(SESSION_KEY) ?? ""; +} + +export function getStoredUser(): AuthUser | null { + const raw = localStorage.getItem(USER_KEY); + if (!raw) return null; + try { + return JSON.parse(raw) as AuthUser; + } catch { + return null; + } +} + +export function setSession(session: AuthSession): void { + localStorage.setItem(SESSION_KEY, session.access_token); + localStorage.setItem(USER_KEY, JSON.stringify(session.user)); + // Mobile WebView listens on this channel; no-op in a regular browser. + postTokenToNativeApp(session.access_token); +} + +export function clearSession(): void { + localStorage.removeItem(SESSION_KEY); + localStorage.removeItem(USER_KEY); +} + +async function apiFetch( + path: string, + init: RequestInit = {}, +): Promise { + const token = getAccessToken(); + const url = path.startsWith("http") ? path : `${API_BASE}${path}`; + const headers = new Headers(init.headers); + headers.set("Accept", "application/json"); + if (!headers.has("Content-Type") && init.body) { + headers.set("Content-Type", "application/json"); + } + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + + const response = await fetch(url, { ...init, headers }); + const raw = await response.text(); + const contentType = response.headers.get("content-type") ?? ""; + + if (response.status === 401) { + clearSession(); + throw new Error("Сессия истекла. Войдите снова."); + } + + if (!response.ok) { + try { + const parsed = JSON.parse(raw) as { detail?: string }; + throw new Error(parsed.detail || raw || `HTTP ${response.status}`); + } catch (err) { + if (err instanceof Error && err.message !== raw) throw err; + throw new Error(raw || `HTTP ${response.status}`); + } + } + + if (response.status === 204 || raw.trim() === "") { + return undefined as T; + } + + if (!contentType.includes("application/json")) { + throw new Error( + `Ожидался JSON с API, а пришло не JSON (${contentType || "без content-type"}).`, + ); + } + + try { + return JSON.parse(raw) as T; + } catch { + throw new Error("Ответ API не удалось разобрать как JSON."); + } +} + +export async function loginWithTelegram( + payload: TelegramLoginPayload, +): Promise { + const session = await apiFetch("/api/auth/telegram", { + method: "POST", + body: JSON.stringify(payload), + }); + setSession(session); + return session; +} + +export async function fetchAuthProviders(): Promise { + return apiFetch("/api/auth/providers"); +} + +export async function loginWithYandex( + code: string, + redirectUri: string, +): Promise { + const session = await apiFetch("/api/auth/yandex", { + method: "POST", + body: JSON.stringify({ code, redirect_uri: redirectUri }), + }); + setSession(session); + return session; +} + +export async function completeTelegramLink(token: string): Promise { + await apiFetch<{ linked: boolean }>("/api/auth/telegram-link/complete", { + method: "POST", + body: JSON.stringify({ token }), + }); +} + +export async function fetchMe(): Promise { + return apiFetch("/api/me"); +} + +export async function fetchMyBudget(budgetId?: number): Promise { + const params = new URLSearchParams(); + if (budgetId != null) params.set("budget_id", String(budgetId)); + const qs = params.toString(); + return apiFetch(`/api/me/budget${qs ? `?${qs}` : ""}`); +} + +export async function fetchMyBudgets(): Promise { + return apiFetch("/api/me/budgets"); +} + +export async function fetchMyExpenses( + page: number, + pageSize = 20, + budgetId?: number | null, + options?: { all?: boolean }, +): Promise { + const params = new URLSearchParams({ + page: String(page), + page_size: String(pageSize), + }); + if (options?.all) { + params.set("all", "true"); + } else if (budgetId != null) { + params.set("budget_id", String(budgetId)); + } + return apiFetch(`/api/me/expenses?${params}`); +} + +export async function fetchMyExpensesRange( + from: string, + to: string, + budgetId?: number, +): Promise { + const params = new URLSearchParams({ from, to }); + if (budgetId != null) params.set("budget_id", String(budgetId)); + return apiFetch(`/api/me/expenses/range?${params}`); +} + +export async function createMyExpense(input: { + amount: number; + note?: string; + spent_at?: string; + budget_id?: number; +}): Promise { + return apiFetch("/api/me/expenses", { + method: "POST", + body: JSON.stringify({ + amount: input.amount, + note: input.note || null, + spent_at: input.spent_at || null, + budget_id: input.budget_id ?? null, + }), + }); +} + +export async function undoMyLastExpense(budgetId?: number): Promise<{ + deleted_amount: number; + status: BudgetStatus; +}> { + const params = new URLSearchParams(); + if (budgetId != null) params.set("budget_id", String(budgetId)); + const qs = params.toString(); + return apiFetch(`/api/me/expenses/last${qs ? `?${qs}` : ""}`, { + method: "DELETE", + }); +} + +export async function createMyBudget(input: { + name: string; + total_amount: number; + end_date: string; + start_date?: string; + is_active?: boolean; + select?: boolean; +}): Promise { + return apiFetch("/api/me/budgets", { + method: "POST", + body: JSON.stringify({ + name: input.name, + total_amount: input.total_amount, + end_date: input.end_date, + start_date: input.start_date || null, + is_active: input.is_active ?? true, + select: input.select ?? true, + }), + }); +} + +export async function updateMyBudget( + budgetId: number, + input: { + name?: string; + total_amount?: number; + end_date?: string; + start_date?: string; + reset_expenses?: boolean; + }, +): Promise { + return apiFetch(`/api/me/budgets/${budgetId}`, { + method: "PUT", + body: JSON.stringify(input), + }); +} + +export async function setMyBudgetActive( + budgetId: number, + isActive: boolean, +): Promise { + return apiFetch(`/api/me/budgets/${budgetId}/active`, { + method: "PATCH", + body: JSON.stringify({ is_active: isActive }), + }); +} + +export async function deleteMyBudget(budgetId: number): Promise { + await apiFetch(`/api/me/budgets/${budgetId}`, { method: "DELETE" }); +} + +export async function selectMyBudget(budgetId: number): Promise { + return apiFetch(`/api/me/budgets/${budgetId}/select`, { + method: "POST", + }); +} + +export async function upsertMyBudget(input: { + total_amount: number; + end_date: string; + reset_expenses: boolean; + name?: string; + budget_id?: number; +}): Promise { + return apiFetch("/api/me/budget", { + method: "PUT", + body: JSON.stringify(input), + }); +} + +export async function fetchMyJobs(): Promise { + return apiFetch("/api/me/jobs"); +} + +export async function createMyJob(input: { + name: string; + salary_amount: number; + pay_days: number[]; + first_pay_percent: number; + weekend_policy: "before_weekend" | "after_weekend"; + is_active?: boolean; +}): Promise { + return apiFetch("/api/me/jobs", { + method: "POST", + body: JSON.stringify({ + name: input.name, + salary_amount: input.salary_amount, + pay_days: input.pay_days, + first_pay_percent: input.first_pay_percent, + weekend_policy: input.weekend_policy, + is_active: input.is_active ?? true, + }), + }); +} + +export async function updateMyJob( + jobId: number, + input: { + name: string; + salary_amount: number; + pay_days: number[]; + first_pay_percent: number; + weekend_policy: "before_weekend" | "after_weekend"; + is_active?: boolean; + }, +): Promise { + return apiFetch(`/api/me/jobs/${jobId}`, { + method: "PUT", + body: JSON.stringify({ + name: input.name, + salary_amount: input.salary_amount, + pay_days: input.pay_days, + first_pay_percent: input.first_pay_percent, + weekend_policy: input.weekend_policy, + is_active: input.is_active ?? true, + }), + }); +} + +export async function deleteMyJob(jobId: number): Promise { + await apiFetch(`/api/me/jobs/${jobId}`, { method: "DELETE" }); +} + +export function formatMoney(amount: number, currency = "RUB"): string { + const symbol = currency === "RUB" ? "₽" : currency; + return `${amount.toLocaleString("ru-RU", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} ${symbol}`; +} + +export function formatDate(iso: string): string { + const [y, m, d] = iso.slice(0, 10).split("-"); + return `${d}.${m}.${y}`; +} + +export function displayName(user: AuthUser | null): string { + if (!user) return "Пользователь"; + if (user.username) return `@${user.username}`; + const full = [user.first_name, user.last_name].filter(Boolean).join(" "); + return full || `user ${user.user_id}`; +} diff --git a/web/src/auth/AuthContext.tsx b/web/src/auth/AuthContext.tsx new file mode 100644 index 0000000..d37434f --- /dev/null +++ b/web/src/auth/AuthContext.tsx @@ -0,0 +1,102 @@ +import { + createContext, + useCallback, + useContext, + useMemo, + useState, + type ReactNode, +} from "react"; +import { + clearSession, + displayName, + getAccessToken, + getStoredUser, + loginWithTelegram, + loginWithYandex, +} from "../api"; +import type { AuthUser, TelegramLoginPayload } from "../types"; +import { isYandexUserId } from "./yandexIdentity"; + +type AuthContextValue = { + user: AuthUser | null; + busy: boolean; + error: string | null; + login: (payload: TelegramLoginPayload) => Promise; + loginYandex: (code: string, redirectUri: string) => Promise; + logout: () => void; + userLabel: string; +}; + +const AuthContext = createContext(null); + +export function AuthProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState(() => { + if (!getAccessToken()) return null; + const stored = getStoredUser(); + if (!stored || !isYandexUserId(stored.user_id)) { + clearSession(); + return null; + } + return stored; + }); + const [error, setError] = useState(null); + const [busy, setBusy] = useState(false); + + const login = useCallback(async (payload: TelegramLoginPayload) => { + try { + setBusy(true); + setError(null); + await loginWithTelegram(payload); + clearSession(); + throw new Error("Вход через Telegram отключён. Войдите через Яндекс."); + } catch (err) { + setError(err instanceof Error ? err.message : "Ошибка входа"); + } finally { + setBusy(false); + } + }, []); + + const loginYandex = useCallback(async (code: string, redirectUri: string) => { + try { + setBusy(true); + setError(null); + const session = await loginWithYandex(code, redirectUri); + if (!isYandexUserId(session.user.user_id)) { + clearSession(); + throw new Error("Войдите через Яндекс"); + } + setUser(session.user); + } catch (err) { + setError(err instanceof Error ? err.message : "Ошибка входа через Яндекс"); + } finally { + setBusy(false); + } + }, []); + + const logout = useCallback(() => { + clearSession(); + setUser(null); + setError(null); + }, []); + + const value = useMemo( + () => ({ + user, + busy, + error, + login, + loginYandex, + logout, + userLabel: user ? displayName(user) : "", + }), + [user, busy, error, login, loginYandex, logout], + ); + + return {children}; +} + +export function useAuth(): AuthContextValue { + const ctx = useContext(AuthContext); + if (!ctx) throw new Error("useAuth must be used within AuthProvider"); + return ctx; +} diff --git a/web/src/auth/RequireAuth.tsx b/web/src/auth/RequireAuth.tsx new file mode 100644 index 0000000..8175aec --- /dev/null +++ b/web/src/auth/RequireAuth.tsx @@ -0,0 +1,29 @@ +import { Navigate, Outlet, useLocation } from "react-router-dom"; +import { AppShell } from "../components/layout/AppShell"; +import { useAuth } from "./AuthContext"; +import { yandexCodeFromQuery, yandexErrorFromQuery } from "./yandexRedirect"; + +export function RequireAuth() { + const { user, logout, userLabel } = useAuth(); + const location = useLocation(); + + if (!user) { + const params = new URLSearchParams(location.search); + const oauthCallback = + yandexCodeFromQuery(location.search) || yandexErrorFromQuery(location.search); + const keepQuery = Boolean(oauthCallback || params.get("tg_link")); + return ( + + ); + } + + return ( + + + + ); +} diff --git a/web/src/auth/TelegramLinkBridge.tsx b/web/src/auth/TelegramLinkBridge.tsx new file mode 100644 index 0000000..3e9cfa5 --- /dev/null +++ b/web/src/auth/TelegramLinkBridge.tsx @@ -0,0 +1,39 @@ +import { useEffect } from "react"; +import { useLocation } from "react-router-dom"; +import { completeTelegramLink } from "../api"; +import { useAuth } from "./AuthContext"; +import { + captureTelegramLinkToken, + clearTelegramLinkToken, + peekTelegramLinkToken, +} from "./telegramLink"; + +export function TelegramLinkBridge() { + const { user } = useAuth(); + const location = useLocation(); + + useEffect(() => { + captureTelegramLinkToken(location.search); + }, [location.search]); + + useEffect(() => { + if (!user) return; + const token = peekTelegramLinkToken(); + if (!token) return; + + let cancelled = false; + void completeTelegramLink(token) + .then(() => { + if (!cancelled) clearTelegramLinkToken(); + }) + .catch(() => { + // Keep the token: user can retry from the bot link. + }); + + return () => { + cancelled = true; + }; + }, [user]); + + return null; +} diff --git a/web/src/auth/telegramLink.ts b/web/src/auth/telegramLink.ts new file mode 100644 index 0000000..f6b09e2 --- /dev/null +++ b/web/src/auth/telegramLink.ts @@ -0,0 +1,46 @@ +const STORAGE_KEY = "ppm_tg_link"; +const TOKEN_RE = /^[a-f0-9]{64}$/i; + +function readStored(): string | null { + try { + const raw = sessionStorage.getItem(STORAGE_KEY)?.trim() ?? ""; + return TOKEN_RE.test(raw) ? raw.toLowerCase() : null; + } catch { + return null; + } +} + +export function peekTelegramLinkToken(): string | null { + return readStored(); +} + +export function captureTelegramLinkToken(search: string): string | null { + const params = new URLSearchParams(search.startsWith("?") ? search : `?${search}`); + const fromQuery = params.get("tg_link")?.trim() ?? ""; + const fromState = params.get("state")?.trim() ?? ""; + const candidate = TOKEN_RE.test(fromQuery) + ? fromQuery + : TOKEN_RE.test(fromState) + ? fromState + : ""; + + if (candidate) { + const token = candidate.toLowerCase(); + try { + sessionStorage.setItem(STORAGE_KEY, token); + } catch { + // Private mode / blocked storage — still return the token for this render. + } + return token; + } + + return readStored(); +} + +export function clearTelegramLinkToken(): void { + try { + sessionStorage.removeItem(STORAGE_KEY); + } catch { + // ignore + } +} diff --git a/web/src/auth/telegramRedirect.ts b/web/src/auth/telegramRedirect.ts new file mode 100644 index 0000000..91a4c79 --- /dev/null +++ b/web/src/auth/telegramRedirect.ts @@ -0,0 +1,52 @@ +import type { TelegramLoginPayload } from "../types"; + +/** + * Telegram Login Widget in redirect mode (`data-auth-url`) appends the + * signed user payload as query parameters. Used by the in-app WebView so + * we never depend on a popup / iframe callback. + */ +export function telegramPayloadFromQuery( + search: string, +): TelegramLoginPayload | null { + const params = new URLSearchParams( + search.startsWith("?") ? search.slice(1) : search, + ); + + const id = Number(params.get("id")); + const hash = params.get("hash"); + const firstName = params.get("first_name"); + const authDate = Number(params.get("auth_date")); + + if (!hash || !firstName || !Number.isFinite(id) || id <= 0) { + return null; + } + + return { + id, + first_name: firstName, + last_name: params.get("last_name") || undefined, + username: params.get("username") || undefined, + photo_url: params.get("photo_url") || undefined, + auth_date: Number.isFinite(authDate) ? authDate : 0, + hash, + }; +} + +/** Flutter injects this JavaScript channel into the cabinet WebView. */ +export function postTokenToNativeApp(token: string): void { + const bridge = ( + window as Window & { PpmAuth?: { postMessage: (message: string) => void } } + ).PpmAuth; + + if (!bridge) return; + + try { + bridge.postMessage(JSON.stringify({ token })); + } catch { + // The channel disappears when the WebView is closing — ignore. + } +} + +export function isNativeAppWebView(): boolean { + return typeof (window as Window & { PpmAuth?: unknown }).PpmAuth !== "undefined"; +} diff --git a/web/src/auth/yandexIdentity.ts b/web/src/auth/yandexIdentity.ts new file mode 100644 index 0000000..0064434 --- /dev/null +++ b/web/src/auth/yandexIdentity.ts @@ -0,0 +1,10 @@ +/** Same bit as `YandexIdentity.NamespaceBit` on the API (`1 << 50`). */ +const YANDEX_NAMESPACE_BIT = 1n << 50n; + +export function isYandexUserId(userId: number): boolean { + try { + return (BigInt(Math.trunc(userId)) & YANDEX_NAMESPACE_BIT) === YANDEX_NAMESPACE_BIT; + } catch { + return false; + } +} diff --git a/web/src/auth/yandexRedirect.ts b/web/src/auth/yandexRedirect.ts new file mode 100644 index 0000000..dd59344 --- /dev/null +++ b/web/src/auth/yandexRedirect.ts @@ -0,0 +1,51 @@ +const YANDEX_AUTHORIZE = "https://oauth.yandex.ru/authorize"; + +export function yandexCodeFromQuery(search: string): string | null { + const params = new URLSearchParams(search.startsWith("?") ? search : `?${search}`); + const code = params.get("code")?.trim(); + if (!code) return null; + // Telegram login also lands on /login with id/hash — never treat that as Yandex. + if (params.has("hash") && params.has("id")) return null; + return code; +} + +export function yandexErrorFromQuery(search: string): string | null { + const params = new URLSearchParams(search.startsWith("?") ? search : `?${search}`); + if (params.has("hash") && params.has("id")) return null; + return params.get("error_description")?.trim() || params.get("error")?.trim() || null; +} + +export function yandexAuthorizeUrl( + clientId: string, + redirectUri: string, + state?: string | null, +): string { + const params = new URLSearchParams({ + response_type: "code", + client_id: clientId, + redirect_uri: redirectUri, + force_confirm: "yes", + }); + if (state) params.set("state", state); + return `${YANDEX_AUTHORIZE}?${params.toString()}`; +} + +/** Must match the Callback URL registered in the Yandex app (trailing slash). */ +export function yandexRedirectUri( + configured?: string | null, + origin = window.location.origin, +): string { + const local = `${origin.replace(/\/$/, "")}/`; + if (!configured) return local; + try { + if (new URL(configured).origin === new URL(local).origin) return configured; + } catch { + // Fall through to the current origin. + } + return local; +} + +/** @deprecated use yandexRedirectUri */ +export function cabinetLoginRedirectUri(origin = window.location.origin): string { + return yandexRedirectUri(null, origin); +} diff --git a/web/src/brand.ts b/web/src/brand.ts new file mode 100644 index 0000000..2103e26 --- /dev/null +++ b/web/src/brand.ts @@ -0,0 +1,5 @@ +export const APP_NAME = "Дожить до ЗП"; + +/** Served from `web/public`. Copy the release APK here before `npm run build`. */ +export const APK_DOWNLOAD_HREF = "/dozhit-do-zp.apk"; +export const APK_DOWNLOAD_NAME = "dozhit-do-zp.apk"; diff --git a/web/src/cabinet/CabinetContext.tsx b/web/src/cabinet/CabinetContext.tsx new file mode 100644 index 0000000..ab6516d --- /dev/null +++ b/web/src/cabinet/CabinetContext.tsx @@ -0,0 +1,509 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, + type FormEvent, + type ReactNode, +} from "react"; +import { useSearchParams } from "react-router-dom"; +import { + createMyBudget, + createMyExpense, + deleteMyBudget, + fetchMyBudget, + fetchMyBudgets, + fetchMyExpenses, + formatMoney, + selectMyBudget, + setMyBudgetActive, + undoMyLastExpense, + updateMyBudget, +} from "../api"; +import { todayIso } from "../lib/date"; +import type { BudgetStatus, ExpensesPage } from "../types"; + +const SELECTED_KEY = "ppm_selected_budget_id"; + +export type JournalScope = "all" | "current"; + +type CabinetContextValue = { + budgets: BudgetStatus[]; + status: BudgetStatus | null; + expenses: ExpensesPage | null; + loading: boolean; + saving: boolean; + noBudget: boolean; + error: string | null; + notice: string | null; + page: number; + setPage: (page: number) => void; + journalScope: JournalScope; + setJournalScope: (scope: JournalScope) => void; + selectedBudgetId: number | null; + selectBudget: (budgetId: number) => Promise; + toggleBudgetActive: (budgetId: number, isActive: boolean) => Promise; + deleteBudget: (budgetId: number) => Promise; + amount: string; + setAmount: (v: string) => void; + note: string; + setNote: (v: string) => void; + spentAt: string; + setSpentAt: (v: string) => void; + expenseBudgetId: number | null; + setExpenseBudgetId: (id: number | null) => void; + budgetName: string; + setBudgetName: (v: string) => void; + budgetAmount: string; + setBudgetAmount: (v: string) => void; + budgetStart: string; + setBudgetStart: (v: string) => void; + budgetEnd: string; + setBudgetEnd: (v: string) => void; + resetExpenses: boolean; + setResetExpenses: (v: boolean) => void; + createMode: boolean; + setCreateMode: (v: boolean) => void; + reload: () => Promise; + onAddExpense: (event: FormEvent) => Promise; + onUndo: () => Promise; + onSaveBudget: (event: FormEvent) => Promise; + clearMessages: () => void; +}; + +const CabinetContext = createContext(null); + +function readStoredBudgetId(): number | null { + const raw = localStorage.getItem(SELECTED_KEY); + if (!raw) return null; + const n = Number(raw); + return Number.isFinite(n) ? n : null; +} + +export function CabinetProvider({ children }: { children: ReactNode }) { + const [searchParams, setSearchParams] = useSearchParams(); + const page = Number(searchParams.get("page") ?? "0") || 0; + const journalScope: JournalScope = + searchParams.get("scope") === "current" ? "current" : "all"; + const journalScopeRef = useRef(journalScope); + journalScopeRef.current = journalScope; + + const [budgets, setBudgets] = useState([]); + const [status, setStatus] = useState(null); + const [expenses, setExpenses] = useState(null); + const [error, setError] = useState(null); + const [notice, setNotice] = useState(null); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [noBudget, setNoBudget] = useState(false); + const [selectedBudgetId, setSelectedBudgetId] = useState( + readStoredBudgetId, + ); + const selectedBudgetIdRef = useRef(selectedBudgetId); + selectedBudgetIdRef.current = selectedBudgetId; + + const [amount, setAmount] = useState(""); + const [note, setNote] = useState(""); + const [spentAt, setSpentAt] = useState(todayIso()); + const [expenseBudgetId, setExpenseBudgetId] = useState(null); + + const [budgetName, setBudgetName] = useState("Бюджет"); + const [budgetAmount, setBudgetAmount] = useState(""); + const [budgetStart, setBudgetStart] = useState(todayIso()); + const [budgetEnd, setBudgetEnd] = useState(""); + const [resetExpenses, setResetExpenses] = useState(false); + const [createMode, setCreateMode] = useState(false); + + const loadExpensesPage = useCallback( + async (pageNum: number, budgetId: number | null | undefined) => { + if (journalScopeRef.current === "all") { + return fetchMyExpenses(pageNum, 20, null, { all: true }); + } + return fetchMyExpenses(pageNum, 20, budgetId ?? undefined); + }, + [], + ); + + const setPage = useCallback( + (next: number) => { + setSearchParams((prev) => { + const params = new URLSearchParams(prev); + if (next > 0) params.set("page", String(next)); + else params.delete("page"); + return params; + }); + }, + [setSearchParams], + ); + + const setJournalScope = useCallback( + (scope: JournalScope) => { + setSearchParams((prev) => { + const params = new URLSearchParams(prev); + if (scope === "current") params.set("scope", "current"); + else params.delete("scope"); + params.delete("page"); + return params; + }); + }, + [setSearchParams], + ); + + const applyStatus = useCallback((next: BudgetStatus) => { + setStatus(next); + setSelectedBudgetId(next.budget.id); + localStorage.setItem(SELECTED_KEY, String(next.budget.id)); + setExpenseBudgetId((prev) => prev ?? next.budget.id); + setBudgetName(next.budget.name); + setBudgetAmount(String(next.budget.total_amount)); + setBudgetStart(next.budget.start_date); + setBudgetEnd(next.budget.end_date); + setNoBudget(false); + setCreateMode(false); + }, []); + + const reload = useCallback(async () => { + setLoading(true); + setError(null); + try { + const list = await fetchMyBudgets(); + setBudgets(list.items); + if (list.items.length === 0) { + setNoBudget(true); + setStatus(null); + setExpenses(null); + setCreateMode(true); + setExpenseBudgetId(null); + return; + } + + const preferred = + list.items.find((item) => item.selected) ?? + list.items.find((item) => item.budget.id === selectedBudgetIdRef.current) ?? + list.items.find((item) => item.budget.is_active) ?? + list.items[0]; + + const budget = await fetchMyBudget(preferred.budget.id); + applyStatus(budget); + setExpenseBudgetId((prev) => { + if (prev && list.items.some((i) => i.budget.id === prev)) return prev; + return budget.budget.id; + }); + setExpenses(await loadExpensesPage(page, budget.budget.id)); + } catch (err) { + const message = err instanceof Error ? err.message : "Ошибка загрузки"; + if (message.includes("Сначала задай бюджет")) { + setNoBudget(true); + setStatus(null); + setExpenses(null); + setBudgets([]); + setCreateMode(true); + setError(null); + } else { + setError(message); + setStatus(null); + setExpenses(null); + } + } finally { + setLoading(false); + } + }, [applyStatus, page, loadExpensesPage, journalScope]); + + useEffect(() => { + void reload(); + }, [reload]); + + useEffect(() => { + if (!notice) return; + const t = window.setTimeout(() => setNotice(null), 3200); + return () => window.clearTimeout(t); + }, [notice]); + + const clearMessages = useCallback(() => { + setError(null); + setNotice(null); + }, []); + + const selectBudget = useCallback( + async (budgetId: number) => { + try { + setSaving(true); + setError(null); + const next = await selectMyBudget(budgetId); + applyStatus(next); + setExpenseBudgetId(budgetId); + setPage(0); + const list = await fetchMyBudgets(); + setBudgets(list.items); + setExpenses(await loadExpensesPage(0, next.budget.id)); + setNotice(`Текущий: ${next.budget.name}`); + } catch (err) { + setError(err instanceof Error ? err.message : "Не удалось выбрать бюджет"); + } finally { + setSaving(false); + } + }, + [applyStatus, setPage, loadExpensesPage], + ); + + const toggleBudgetActive = useCallback( + async (budgetId: number, isActive: boolean) => { + try { + setSaving(true); + setError(null); + await setMyBudgetActive(budgetId, isActive); + await reload(); + setNotice(isActive ? "Бюджет включён" : "Бюджет выключен"); + } catch (err) { + setError(err instanceof Error ? err.message : "Не удалось изменить статус"); + } finally { + setSaving(false); + } + }, + [reload], + ); + + const deleteBudget = useCallback( + async (budgetId: number) => { + try { + setSaving(true); + setError(null); + await deleteMyBudget(budgetId); + localStorage.removeItem(SELECTED_KEY); + setSelectedBudgetId(null); + setPage(0); + await reload(); + setNotice("Бюджет удалён"); + } catch (err) { + setError(err instanceof Error ? err.message : "Не удалось удалить бюджет"); + } finally { + setSaving(false); + } + }, + [reload, setPage], + ); + + const onAddExpense = useCallback( + async (event: FormEvent) => { + event.preventDefault(); + const value = Number(amount.replace(",", ".")); + if (!Number.isFinite(value) || value <= 0) { + setError("Укажи сумму больше нуля"); + return; + } + const targetBudgetId = expenseBudgetId ?? selectedBudgetId; + if (targetBudgetId == null) { + setError("Выбери бюджет для траты"); + return; + } + try { + setSaving(true); + setError(null); + setNotice(null); + const next = await createMyExpense({ + amount: value, + note: note.trim() || undefined, + spent_at: spentAt || undefined, + budget_id: targetBudgetId, + }); + applyStatus(next); + setAmount(""); + setNote(""); + setSpentAt(todayIso()); + setNotice(`Записал ${formatMoney(value)}`); + setPage(0); + setExpenses(await loadExpensesPage(0, next.budget.id)); + setBudgets((await fetchMyBudgets()).items); + } catch (err) { + setError(err instanceof Error ? err.message : "Не удалось сохранить трату"); + } finally { + setSaving(false); + } + }, + [ + amount, + note, + spentAt, + expenseBudgetId, + selectedBudgetId, + applyStatus, + setPage, + loadExpensesPage, + ], + ); + + const onUndo = useCallback(async () => { + try { + setSaving(true); + setError(null); + setNotice(null); + const targetBudgetId = + journalScopeRef.current === "all" + ? undefined + : (expenseBudgetId ?? selectedBudgetId ?? undefined); + const result = await undoMyLastExpense(targetBudgetId); + applyStatus(result.status); + setNotice(`Удалил ${formatMoney(result.deleted_amount)}`); + setExpenses(await loadExpensesPage(page, result.status.budget.id)); + setBudgets((await fetchMyBudgets()).items); + } catch (err) { + setError(err instanceof Error ? err.message : "Нечего отменять"); + } finally { + setSaving(false); + } + }, [selectedBudgetId, expenseBudgetId, applyStatus, page, loadExpensesPage]); + + const onSaveBudget = useCallback( + async (event: FormEvent) => { + event.preventDefault(); + const value = Number(budgetAmount.replace(",", ".")); + if (!Number.isFinite(value) || value <= 0) { + setError("Сумма бюджета должна быть больше нуля"); + return; + } + if (!budgetEnd) { + setError("Укажи дату окончания периода"); + return; + } + const start = budgetStart || todayIso(); + if (budgetEnd < start) { + setError("Дата окончания не может быть раньше даты начала"); + return; + } + const name = budgetName.trim() || "Бюджет"; + try { + setSaving(true); + setError(null); + setNotice(null); + const next = + createMode || !selectedBudgetId + ? await createMyBudget({ + name, + total_amount: value, + start_date: start, + end_date: budgetEnd, + is_active: true, + select: true, + }) + : await updateMyBudget(selectedBudgetId, { + name, + total_amount: value, + start_date: start, + end_date: budgetEnd, + reset_expenses: resetExpenses, + }); + applyStatus(next); + setNotice(createMode ? "Бюджет создан" : "Бюджет сохранён"); + setPage(0); + setExpenses(await loadExpensesPage(0, next.budget.id)); + setBudgets((await fetchMyBudgets()).items); + } catch (err) { + setError(err instanceof Error ? err.message : "Не удалось сохранить бюджет"); + } finally { + setSaving(false); + } + }, + [ + budgetAmount, + budgetStart, + budgetEnd, + budgetName, + createMode, + selectedBudgetId, + resetExpenses, + applyStatus, + setPage, + loadExpensesPage, + ], + ); + + const value = useMemo( + () => ({ + budgets, + status, + expenses, + loading, + saving, + noBudget, + error, + notice, + page, + setPage, + journalScope, + setJournalScope, + selectedBudgetId, + selectBudget, + toggleBudgetActive, + deleteBudget, + amount, + setAmount, + note, + setNote, + spentAt, + setSpentAt, + expenseBudgetId, + setExpenseBudgetId, + budgetName, + setBudgetName, + budgetAmount, + setBudgetAmount, + budgetStart, + setBudgetStart, + budgetEnd, + setBudgetEnd, + resetExpenses, + setResetExpenses, + createMode, + setCreateMode, + reload, + onAddExpense, + onUndo, + onSaveBudget, + clearMessages, + }), + [ + budgets, + status, + expenses, + loading, + saving, + noBudget, + error, + notice, + page, + setPage, + journalScope, + setJournalScope, + selectedBudgetId, + selectBudget, + toggleBudgetActive, + deleteBudget, + amount, + note, + spentAt, + expenseBudgetId, + budgetName, + budgetAmount, + budgetStart, + budgetEnd, + resetExpenses, + createMode, + reload, + onAddExpense, + onUndo, + onSaveBudget, + clearMessages, + ], + ); + + return {children}; +} + +export function useCabinet(): CabinetContextValue { + const ctx = useContext(CabinetContext); + if (!ctx) throw new Error("useCabinet must be used within CabinetProvider"); + return ctx; +} diff --git a/web/src/cabinet/CabinetLayout.tsx b/web/src/cabinet/CabinetLayout.tsx new file mode 100644 index 0000000..8f57639 --- /dev/null +++ b/web/src/cabinet/CabinetLayout.tsx @@ -0,0 +1,22 @@ +import { Outlet } from "react-router-dom"; +import { Flash } from "../components/ui"; +import { CabinetProvider, useCabinet } from "./CabinetContext"; + +function CabinetFrame() { + const { error, notice } = useCabinet(); + return ( +
+ + +
+ ); +} + +/** Authenticated product area: shared cabinet state + page chrome. */ +export function CabinetLayout() { + return ( + + + + ); +} diff --git a/web/src/components/layout/AppShell.tsx b/web/src/components/layout/AppShell.tsx new file mode 100644 index 0000000..bebf802 --- /dev/null +++ b/web/src/components/layout/AppShell.tsx @@ -0,0 +1,75 @@ +import { NavLink } from "react-router-dom"; +import type { ReactNode } from "react"; +import { APP_NAME } from "../../brand"; +import { SiteFooter } from "../../legal/SiteFooter"; +import { AppAvatar, AppButton, AppText } from "../../ui"; +import { PRIMARY_NAV } from "./nav"; + +type Props = { + brand?: string; + userLabel: string; + onLogout: () => void; + children: ReactNode; +}; + +function initials(label: string): string { + const parts = label.trim().split(/\s+/).filter(Boolean); + if (parts.length === 0) return "•"; + if (parts.length === 1) return parts[0].slice(0, 1).toUpperCase(); + return (parts[0][0] + parts[1][0]).toUpperCase(); +} + +export function AppShell({ + brand = APP_NAME, + userLabel, + onLogout, + children, +}: Props) { + return ( +
+
+ + {brand} + +
+ + + {userLabel} + + + Выйти + +
+
+ + + +
{children}
+
+ ); +} diff --git a/web/src/components/layout/AuthLayout.tsx b/web/src/components/layout/AuthLayout.tsx new file mode 100644 index 0000000..17eeb0c --- /dev/null +++ b/web/src/components/layout/AuthLayout.tsx @@ -0,0 +1,38 @@ +import type { ReactNode } from "react"; +import { APP_NAME } from "../../brand"; +import { AppText } from "../../ui"; + +type Props = { + brand?: string; + title: string; + lead: string; + cta: ReactNode; + footer?: ReactNode; + flash?: ReactNode; +}; + +export function AuthLayout({ + brand = APP_NAME, + title, + lead, + cta, + footer, + flash, +}: Props) { + return ( +
+
+ + {brand} + + + {title} + + {lead} +
{cta}
+ {flash} + {footer ?
{footer}
: null} +
+
+ ); +} diff --git a/web/src/components/layout/nav.ts b/web/src/components/layout/nav.ts new file mode 100644 index 0000000..4401011 --- /dev/null +++ b/web/src/components/layout/nav.ts @@ -0,0 +1,20 @@ +export type NavItem = { + id: string; + to: string; + label: string; + /** Future modules: visible but not routed yet */ + soon?: boolean; +}; + +/** Primary product navigation — append items here as features land. */ +export const PRIMARY_NAV: NavItem[] = [ + { id: "overview", to: "/", label: "Обзор" }, + { id: "budgets", to: "/budgets", label: "Бюджеты" }, + { id: "work", to: "/work", label: "Работа" }, + { id: "calendar", to: "/calendar", label: "Календарь" }, + { id: "operations", to: "/operations", label: "Операции" }, + { id: "journal", to: "/journal", label: "Журнал" }, + { id: "period", to: "/period", label: "Период" }, + { id: "reports", to: "/reports", label: "Отчёты", soon: true }, + { id: "settings", to: "/settings", label: "Настройки", soon: true }, +]; diff --git a/web/src/components/ui/Banner.tsx b/web/src/components/ui/Banner.tsx new file mode 100644 index 0000000..79f44ef --- /dev/null +++ b/web/src/components/ui/Banner.tsx @@ -0,0 +1,14 @@ +import type { ReactNode } from "react"; + +type Props = { + children: ReactNode; + tone?: "warn"; +}; + +export function Banner({ children, tone = "warn" }: Props) { + return ( +

+ {children} +

+ ); +} diff --git a/web/src/components/ui/Button.tsx b/web/src/components/ui/Button.tsx new file mode 100644 index 0000000..168abfc --- /dev/null +++ b/web/src/components/ui/Button.tsx @@ -0,0 +1,45 @@ +import type { ButtonHTMLAttributes, ReactNode } from "react"; +import { AppButton, type AppButtonSize, type AppButtonStyle } from "../../ui"; + +type Variant = "primary" | "secondary" | "ghost" | "destructive"; +type Size = "md" | "sm"; + +const STYLE: Record = { + primary: "filled", + secondary: "gray", + ghost: "plain", + destructive: "destructive", +}; + +const SIZE: Record = { + md: "medium", + sm: "small", +}; + +type Props = ButtonHTMLAttributes & { + variant?: Variant; + size?: Size; + expanded?: boolean; + children: ReactNode; +}; + +export function Button({ + variant = "secondary", + size = "md", + expanded = false, + className = "", + children, + ...rest +}: Props) { + return ( + + {children} + + ); +} diff --git a/web/src/components/ui/EmptyState.tsx b/web/src/components/ui/EmptyState.tsx new file mode 100644 index 0000000..45d6af1 --- /dev/null +++ b/web/src/components/ui/EmptyState.tsx @@ -0,0 +1,10 @@ +import type { ReactNode } from "react"; +import { AppEmptyView } from "../../ui"; + +type Props = { + children: ReactNode; +}; + +export function EmptyState({ children }: Props) { + return ; +} diff --git a/web/src/components/ui/Field.tsx b/web/src/components/ui/Field.tsx new file mode 100644 index 0000000..d1fd25c --- /dev/null +++ b/web/src/components/ui/Field.tsx @@ -0,0 +1,12 @@ +import type { InputHTMLAttributes, ReactNode } from "react"; +import { AppTextField } from "../../ui"; + +type Props = InputHTMLAttributes & { + label: string; + id: string; + hint?: ReactNode; +}; + +export function Field({ label, id, hint, className = "", ...rest }: Props) { + return ; +} diff --git a/web/src/components/ui/Flash.tsx b/web/src/components/ui/Flash.tsx new file mode 100644 index 0000000..baa2aec --- /dev/null +++ b/web/src/components/ui/Flash.tsx @@ -0,0 +1,14 @@ +type Props = { + error?: string | null; + notice?: string | null; +}; + +export function Flash({ error = null, notice = null }: Props) { + if (!error && !notice) return null; + return ( +
+ {error ?

{error}

: null} + {notice ?

{notice}

: null} +
+ ); +} diff --git a/web/src/components/ui/MetricGrid.tsx b/web/src/components/ui/MetricGrid.tsx new file mode 100644 index 0000000..1728d52 --- /dev/null +++ b/web/src/components/ui/MetricGrid.tsx @@ -0,0 +1,26 @@ +import type { ReactNode } from "react"; +import { AppListSection, AppListTile } from "../../ui"; + +export type MetricItem = { + label: string; + value: ReactNode; + warn?: boolean; +}; + +type Props = { + items: MetricItem[]; +}; + +export function MetricGrid({ items }: Props) { + return ( + + {items.map((item) => ( + {item.value}} + /> + ))} + + ); +} diff --git a/web/src/components/ui/PageHeader.tsx b/web/src/components/ui/PageHeader.tsx new file mode 100644 index 0000000..b3a2a9b --- /dev/null +++ b/web/src/components/ui/PageHeader.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from "react"; +import { AppText } from "../../ui"; + +type Props = { + eyebrow?: string; + title: string; + description?: ReactNode; +}; + +export function PageHeader({ eyebrow, title, description }: Props) { + return ( +
+ {eyebrow ? {eyebrow} : null} + + {title} + + {description ? {description} : null} +
+ ); +} diff --git a/web/src/components/ui/ProgressBar.tsx b/web/src/components/ui/ProgressBar.tsx new file mode 100644 index 0000000..ba646b1 --- /dev/null +++ b/web/src/components/ui/ProgressBar.tsx @@ -0,0 +1,17 @@ +import { AppProgress, AppText } from "../../ui"; + +type Props = { + spent: number; + total: number; + label?: string; +}; + +export function ProgressBar({ spent, total, label }: Props) { + const pct = total > 0 ? Math.min(100, Math.max(0, (spent / total) * 100)) : 0; + return ( +
+ + {Math.round(pct)}% бюджета использовано +
+ ); +} diff --git a/web/src/components/ui/Section.tsx b/web/src/components/ui/Section.tsx new file mode 100644 index 0000000..fc9811d --- /dev/null +++ b/web/src/components/ui/Section.tsx @@ -0,0 +1,28 @@ +import type { ReactNode } from "react"; +import { AppSectionHeader } from "../../ui"; + +type Props = { + title: string; + description?: ReactNode; + actions?: ReactNode; + children: ReactNode; + labelledBy?: string; +}; + +export function Section({ title, description, actions, children, labelledBy }: Props) { + const id = labelledBy ?? `section-${title.toLowerCase().replace(/\s+/g, "-")}`; + return ( +
+
+
+

+ {title} +

+ {description ?

{description}

: null} +
+ {actions} +
+ {children} +
+ ); +} diff --git a/web/src/components/ui/index.ts b/web/src/components/ui/index.ts new file mode 100644 index 0000000..c962fe9 --- /dev/null +++ b/web/src/components/ui/index.ts @@ -0,0 +1,10 @@ +export { Button } from "./Button"; +export { Field } from "./Field"; +export { Flash } from "./Flash"; +export { ProgressBar } from "./ProgressBar"; +export { MetricGrid } from "./MetricGrid"; +export { PageHeader } from "./PageHeader"; +export { Section } from "./Section"; +export { EmptyState } from "./EmptyState"; +export { Banner } from "./Banner"; +export * from "../../ui"; diff --git a/web/src/design/base.css b/web/src/design/base.css new file mode 100644 index 0000000..8790320 --- /dev/null +++ b/web/src/design/base.css @@ -0,0 +1,70 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body, +#root { + min-height: 100%; +} + +body { + margin: 0; + font-family: var(--font-sans); + font-size: var(--text-body); + letter-spacing: -0.41px; + color: var(--label); + background: var(--grouped-background); + line-height: 1.3; + -webkit-font-smoothing: antialiased; +} + +a { + color: inherit; + text-decoration: none; +} + +button, +input { + font: inherit; +} + +h1, +h2, +h3 { + margin: 0; + font-weight: 600; + letter-spacing: -0.02em; +} + +p { + margin: 0; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +.muted { + color: var(--color-muted); +} + +.mono { + font-family: var(--font-mono); +} + +.is-warn { + color: var(--color-warn) !important; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation: none !important; + transition: none !important; + } +} diff --git a/web/src/design/components.css b/web/src/design/components.css new file mode 100644 index 0000000..2ce3175 --- /dev/null +++ b/web/src/design/components.css @@ -0,0 +1,989 @@ +/* —— Layout shell —— */ +.app-shell { + min-height: 100vh; + display: grid; + grid-template-columns: var(--sidebar-width) minmax(0, 1fr); + grid-template-rows: auto 1fr; + grid-template-areas: + "header header" + "nav main"; + background: var(--grouped-background); +} + +.app-shell__header { + grid-area: header; + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + min-height: 44px; + padding: 0 var(--gutter); + border-bottom: var(--hairline) solid var(--separator); + background: var(--bar-background); + backdrop-filter: saturate(180%) blur(20px); + position: sticky; + top: 0; + z-index: var(--z-header); +} + +.app-shell__brand { + font-size: var(--text-md); + font-weight: 700; + letter-spacing: -0.01em; +} + +.app-shell__user { + display: flex; + align-items: center; + gap: var(--space-3); +} + +.app-shell__chip { + max-width: 12rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--color-muted); + font-size: var(--text-sm); +} + +.app-shell__nav { + grid-area: nav; + padding: var(--space-2) 0 var(--space-4); + position: sticky; + top: 44px; + height: calc(100vh - 44px); + overflow: auto; +} + +.app-shell__nav-label { + margin: 0 var(--space-2) var(--space-2); + font-size: var(--text-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--color-muted); +} + +.app-shell__nav-list { + list-style: none; + margin: 0 var(--gutter); + padding: 0; + display: grid; + background: var(--grouped-surface); + border-radius: var(--radius-lg); + overflow: hidden; +} + +.app-shell__nav-link { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-2); + min-height: var(--row-min-height); + padding: 0 var(--gutter); + color: var(--label); + font-size: var(--text-body); + letter-spacing: -0.41px; +} + +.app-shell__nav-link:hover { + background: var(--system-gray5); +} + +.app-shell__nav-link.is-active { + color: var(--accent); + font-weight: 600; +} + +.app-shell__nav-link.is-disabled { + opacity: 0.45; + pointer-events: none; +} + +.app-shell__badge { + font-size: var(--text-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--color-muted); +} + +.app-shell__main { + grid-area: main; + min-width: 0; +} + +.page { + width: min(var(--content-max), 100%); + margin: 0 auto; + padding: var(--space-5) 0 var(--space-8); +} + +.page > .app-text, +.page > .app-btn { + margin-inline: var(--gutter); +} + +.page-header { + margin-bottom: var(--space-5); + padding: 0 var(--gutter); + display: grid; + gap: var(--space-1); +} + +.page-header__eyebrow { + margin: 0 0 var(--space-2); + font-size: var(--text-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--color-muted); +} + +.page-header__title { + font-size: var(--text-xl); + line-height: 1.2; +} + +.page-header__desc { + margin-top: var(--space-2); + color: var(--color-muted); + font-size: var(--text-md); + max-width: 36rem; +} + +.section { + margin: 0 0 var(--space-6); +} + +.section:first-child { + border-top: 0; + padding-top: 0; +} + +.section__head { + margin-bottom: var(--space-2); + padding-right: var(--gutter); +} + +.section__head--row { + display: flex; + justify-content: space-between; + gap: var(--space-4); + align-items: end; +} + +.section__title { + margin: 0; +} + +.section__title .app-section-header { + padding-bottom: 0; +} + +.section__desc { + margin: 0; + padding: 0 var(--gutter); + color: var(--secondary-label); + font-size: var(--text-footnote); +} + +/* —— Auth layout —— */ +.auth-layout { + min-height: 100vh; + display: grid; + place-items: center; + padding: var(--space-5); + background: var(--color-bg); +} + +.auth-panel { + width: min(400px, 100%); + padding: var(--space-6) var(--gutter); + display: grid; + gap: var(--space-3); +} + +.auth-panel__brand { + margin: 0 0 var(--space-5); + color: var(--color-accent); + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: var(--text-xs); + font-weight: 600; +} + +.auth-panel__title { + font-size: var(--text-xl); + line-height: 1.25; +} + +.auth-panel__lead { + margin-top: var(--space-2); + color: var(--color-muted); + font-size: var(--text-md); +} + +.auth-panel__cta { + display: grid; + gap: var(--space-3); + margin-top: var(--space-5); + min-height: 48px; +} + +.auth-divider { + margin: 0; + color: var(--color-muted); + font-size: var(--text-sm); + text-align: center; +} + +.auth-panel__foot { + margin-top: var(--space-5); + padding-top: var(--space-4); + border-top: 1px solid var(--color-line); + color: var(--color-muted); + font-size: var(--text-sm); +} + +.auth-panel__foot code { + font-family: var(--font-mono); + font-size: 0.85em; + color: var(--color-ink); +} + +.tg-login { + min-height: 44px; +} + +.btn--yandex { + width: 100%; + gap: var(--space-2); + background: #000; + color: #fff; + text-decoration: none; +} + +.btn--yandex:hover:not(:disabled) { + background: #1a1a1a; + color: #fff; + border-radius: 8px; +} + +.btn--yandex__mark { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.25rem; + height: 1.25rem; + border-radius: 8px; + background: #fc3f1d; + color: #fff; + font-size: 0.75rem; + font-weight: 700; + line-height: 1; +} + +/* —— UI primitives —— */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + height: var(--control-height-md); + padding: 0 var(--space-4); + border-radius: var(--radius-lg); + border: 0; + font-weight: 600; + font-size: var(--text-body); + cursor: pointer; + background: var(--system-gray5); + color: var(--label); +} + +.btn:disabled { + opacity: 0.45; + cursor: not-allowed; +} + +.btn--primary { + background: var(--accent); + color: #fff; +} + +.btn--primary:hover:not(:disabled) { + filter: brightness(1.04); +} + +.btn--secondary { + background: var(--system-gray5); + color: var(--label); +} + +.btn--ghost { + background: transparent; + color: var(--accent); +} + +.btn--sm { + height: var(--control-height-sm); + padding: 0 var(--space-3); + font-size: var(--text-sm); +} + +.field { + display: grid; + gap: var(--space-2); +} + +.field__label { + color: var(--color-ink-secondary); + font-size: var(--text-xs); + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.field__control { + width: 100%; + height: var(--control-height-md); + border: 0; + background: var(--grouped-surface); + color: var(--label); + border-radius: var(--radius-md); + padding: 0 var(--space-3); +} + +.field__control:hover:not(:disabled) { + border-color: var(--color-ink-secondary); +} + +.field__control:disabled { + background: var(--color-bg); + color: var(--color-muted); +} + +.form { + display: grid; + gap: var(--space-4); + padding-inline: var(--gutter); +} + +.form__row { + display: grid; + grid-template-columns: 1.4fr 1fr; + gap: var(--space-3); +} + +.form__actions { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + align-items: center; +} + +.data-list__row .form__actions { + flex-shrink: 0; + justify-content: flex-end; +} + +.check { + display: flex; + align-items: flex-start; + gap: var(--space-2); + color: var(--color-ink-secondary); + font-size: var(--text-md); + cursor: pointer; +} + +.check input { + margin-top: 0.15rem; + accent-color: var(--color-accent); +} + +.flash { + display: grid; + gap: var(--space-2); + margin-bottom: var(--space-4); +} + +.flash { + margin-inline: var(--gutter); +} + +.flash__item { + margin: 0; + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-lg); + font-size: var(--text-subhead); +} + +.flash__item--error { + color: var(--color-danger); + background: var(--color-danger-bg); + border-color: #e2bcbc; +} + +.flash__item--ok { + color: var(--color-ok); + background: var(--color-ok-bg); + border-color: #b9d8c7; +} + +.banner { + margin: var(--space-4) var(--gutter) 0; + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-lg); + font-size: var(--text-subhead); +} + +.banner--warn { + color: var(--color-warn); + background: var(--color-warn-bg); + border-color: #e2d4a8; +} + +.metric-grid { + margin: var(--space-5) 0 0; + padding: 0; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + border: 1px solid var(--color-line); + background: var(--color-surface); +} + +.metric-grid > div { + padding: var(--space-3) var(--space-4); + border-right: 1px solid var(--color-line); +} + +.metric-grid > div:last-child { + border-right: 0; +} + +.metric-grid dt { + margin: 0; + color: var(--color-muted); + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 500; +} + +.metric-grid dd { + margin: var(--space-1) 0 0; + font-family: var(--font-mono); + font-size: var(--text-base); + font-weight: 500; +} + +.progress { + margin: var(--space-4) var(--gutter) 0; + display: grid; + gap: var(--space-2); +} + +.progress__track { + height: 4px; + background: var(--color-line); + overflow: hidden; +} + +.progress__bar { + display: block; + height: 100%; + background: var(--color-accent); +} + +.progress__meta { + margin-top: var(--space-2); + font-size: var(--text-xs); + font-family: var(--font-mono); + color: var(--color-muted); +} + +.hero-metric__label { + margin-top: var(--space-4); + color: var(--color-muted); + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 500; +} + +.hero-metric__label, +.hero-metric__value { + padding-inline: var(--gutter); +} + +.meta-row { + margin: 0; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-2); + font-size: var(--text-xs); + font-weight: 500; + color: var(--color-muted); + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.meta-row__dot { + width: 3px; + height: 3px; + border-radius: 50%; + background: var(--color-line-strong); +} + +.data-list { + list-style: none; + margin: 0 var(--gutter); + padding: 0; + background: var(--grouped-surface); + border-radius: var(--radius-lg); + overflow: hidden; +} + +.data-list__row { + display: flex; + justify-content: space-between; + gap: var(--space-4); + align-items: baseline; + min-height: var(--row-min-height); + padding: 10px var(--gutter); + box-shadow: inset 0 var(--hairline) 0 var(--separator); +} + +.data-list__row:last-child { + border-bottom: 0; +} + +.data-list__primary { + display: block; + font-size: var(--text-body); + font-weight: 400; + letter-spacing: -0.41px; + font-variant-numeric: tabular-nums; +} + +.data-list__secondary { + display: block; + margin-top: 0.1rem; + font-size: var(--text-sm); + color: var(--color-muted); +} + +.data-list__meta { + color: var(--color-muted); + font-family: var(--font-mono); + font-size: var(--text-xs); + white-space: nowrap; +} + +.empty-state { + margin: 0 var(--gutter); + padding: var(--space-6) var(--gutter); + color: var(--secondary-label); + font-size: var(--text-subhead); + text-align: center; +} + +.pager { + display: flex; + align-items: center; + gap: var(--space-3); + margin-top: var(--space-4); + padding-inline: var(--gutter); + font-size: var(--text-footnote); +} + +.panel { + border: 1px solid var(--color-line); + background: var(--color-surface); +} + +.panel__head { + padding: var(--space-4); + border-bottom: 1px solid var(--color-line); + font-size: var(--text-sm); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.panel__body { + padding: var(--space-4); +} + +.coming-soon { + border: 1px solid var(--color-line); + background: var(--color-surface); + padding: var(--space-6); +} + +.coming-soon__title { + font-size: var(--text-lg); +} + +.coming-soon__text { + margin-top: var(--space-2); + color: var(--color-muted); + font-size: var(--text-md); + max-width: 28rem; +} + +.skel { + background: var(--color-line); + border-radius: var(--radius-sm); +} + +.skel-line { + height: 0.75rem; +} + +.skel-title { + height: 2rem; + width: min(220px, 55%); + margin: var(--space-3) 0; +} + +.w-20 { + width: 4.5rem; +} +.w-24 { + width: 5.5rem; +} +.w-32 { + width: 7rem; +} +.w-56 { + width: 12rem; +} +.mt-2 { + margin-top: var(--space-2); +} + +.field__hint { + margin: 0; + font-size: var(--text-sm); +} + +.day-chips { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.day-chip { + min-width: 2.5rem; + height: 28px; + padding: 0 var(--space-3); + border: 0; + border-radius: var(--radius-capsule); + background: var(--system-gray5); + color: var(--label); + font-size: var(--text-subhead); + cursor: pointer; +} + +.day-chip:hover:not(:disabled) { + border-color: var(--color-ink-secondary); +} + +.day-chip.is-on { + background: rgb(18 136 90 / 15%); + color: var(--accent); +} + +.day-chip:disabled { + opacity: 0.45; + cursor: not-allowed; +} + +.choice-set { + margin: 0; + padding: 0; + border: 0; + display: grid; + gap: var(--space-2); +} + +.choice-set legend { + margin-bottom: var(--space-1); +} + +.split-block { + display: grid; + gap: var(--space-2); +} + +@media (max-width: 860px) { + .app-shell { + grid-template-columns: 1fr; + grid-template-areas: + "header" + "nav" + "main"; + } + + .app-shell__nav { + position: static; + height: auto; + border-right: 0; + border-bottom: 1px solid var(--color-line); + padding: var(--space-3); + } + + .app-shell__nav-label { + display: none; + } + + .app-shell__nav-list { + display: flex; + gap: var(--space-2); + overflow-x: auto; + background: transparent; + margin: 0 var(--gutter); + } + + .app-shell__nav-link { + white-space: nowrap; + background: var(--system-gray5); + border-radius: var(--radius-capsule); + min-height: 32px; + padding: 0 12px; + font-size: var(--text-subhead); + } + + .app-shell__nav-link.is-active { + background: var(--grouped-surface); + } + + .page { + width: min(var(--content-max), calc(100% - 2 * var(--space-4))); + } + + .metric-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .metric-grid > div:nth-child(2) { + border-right: 0; + } + + .metric-grid > div:nth-child(3), + .metric-grid > div:nth-child(4) { + border-top: 1px solid var(--color-line); + } + + .form__row { + grid-template-columns: 1fr; + } + + .section__head--row { + flex-direction: column; + align-items: start; + } + + .cal__day { + min-height: 3.5rem; + } +} + +/* —— Calendar —— */ +.cal { + display: grid; + gap: var(--space-3); +} + +.cal__weekdays, +.cal__grid { + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + gap: 2px; +} + +.cal__weekday { + text-align: center; + font-size: var(--text-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--color-muted); + padding: var(--space-1) 0; +} + +.cal__day { + position: relative; + display: flex; + flex-direction: column; + align-items: stretch; + gap: 2px; + min-height: 4.25rem; + padding: var(--space-2) var(--space-2) var(--space-1); + margin: 0; + border: 0; + border-radius: var(--radius-md); + background: var(--grouped-surface); + color: var(--label); + text-align: left; + cursor: pointer; + font: inherit; +} + +.cal__day:hover { + border-color: var(--color-line-strong); +} + +.cal__day--out { + opacity: 0.42; +} + +.cal__day--today { + border-color: var(--color-accent); + background: var(--color-accent-soft); +} + +.cal__day--selected { + outline: 2px solid var(--color-ink); + outline-offset: -1px; + z-index: 1; +} + +.cal__day-num { + font-size: var(--text-sm); + font-weight: 600; + font-variant-numeric: tabular-nums; + line-height: 1.2; +} + +.cal__day-top { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-1); +} + +.cal__spend-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--color-ink); + flex-shrink: 0; +} + +.cal__spend-sum { + font-size: var(--text-xs); + font-weight: 600; + font-variant-numeric: tabular-nums; + color: var(--color-ink-secondary); + line-height: 1.2; + letter-spacing: -0.02em; +} + +.cal__day--spent .cal__spend-sum { + color: var(--color-ink); +} + +.cal-day-block + .cal-day-block { + margin-top: var(--space-4); + padding-top: var(--space-4); + border-top: 1px solid var(--color-line); +} + +.cal-day-block__title { + margin: 0 0 var(--space-2); + font-size: var(--text-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-muted); +} + +.cal-legend__swatch--spend { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--color-ink); + margin-top: 5px; + margin-left: 4px; + margin-right: 4px; +} + +.cal__stripes { + display: flex; + flex-direction: column; + gap: 2px; + flex: 1; + min-height: 0; + margin-top: 2px; +} + +.cal__stripe { + display: block; + height: 5px; + border-radius: 1px; + flex: 1; + min-height: 4px; + max-height: 8px; +} + +.cal__pays { + display: flex; + gap: 3px; + justify-content: flex-end; + align-items: center; + min-height: 8px; +} + +.cal__pay { + width: 8px; + height: 8px; + border-radius: 50%; + border: 1.5px solid var(--color-surface); + box-shadow: 0 0 0 1px rgba(18, 23, 20, 0.2); + flex-shrink: 0; +} + +.cal-legend { + list-style: none; + margin: 0; + padding: 0; + display: grid; + gap: var(--space-2); +} + +.cal-legend__item { + display: flex; + align-items: flex-start; + gap: var(--space-2); + font-size: var(--text-sm); + color: var(--color-ink-secondary); +} + +.cal-legend__swatch { + display: inline-block; + width: 14px; + height: 14px; + flex-shrink: 0; + margin-top: 2px; + border-radius: var(--radius-sm); +} + +.cal-legend__swatch--budget { + border: 1px solid transparent; +} + +.cal-legend__swatch--pay { + width: 10px; + height: 10px; + border-radius: 50%; + margin-top: 4px; + margin-left: 2px; + margin-right: 2px; +} + +.cal-legend__meta { + color: var(--color-muted); +} diff --git a/web/src/design/index.css b/web/src/design/index.css new file mode 100644 index 0000000..db6cb1b --- /dev/null +++ b/web/src/design/index.css @@ -0,0 +1,5 @@ +@import "./tokens.css"; +@import "./base.css"; +@import "../ui/kit.css"; +@import "./components.css"; +@import "../legal/legal.css"; diff --git a/web/src/design/tokens.css b/web/src/design/tokens.css new file mode 100644 index 0000000..46cc69a --- /dev/null +++ b/web/src/design/tokens.css @@ -0,0 +1,117 @@ +:root { + /* 4pt grid — same as mobile/lib/theme/tokens.dart */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 28px; + --space-7: 40px; + --space-8: 56px; + --gutter: 16px; + + /* SF Pro scale (Apple HIG), rendered in Inter */ + --text-caption2: 11px; + --text-caption1: 12px; + --text-footnote: 13px; + --text-subhead: 15px; + --text-callout: 16px; + --text-body: 17px; + --text-headline: 17px; + --text-title3: 20px; + --text-title2: 22px; + --text-title1: 28px; + --text-large-title: 34px; + + /* Legacy aliases used by older product CSS */ + --text-xs: var(--text-caption2); + --text-sm: var(--text-footnote); + --text-md: var(--text-subhead); + --text-base: var(--text-body); + --text-lg: var(--text-title3); + --text-xl: var(--text-title2); + --text-2xl: var(--text-large-title); + + /* Light — CupertinoDynamicColor.color */ + --accent: #12885a; + --accent-soft: #e4f4ec; + --system-red: #ff3b30; + --system-orange: #ff9500; + --system-green: #34c759; + --system-gray: #8e8e93; + --system-gray3: #c7c7cc; + --system-gray5: #e5e5ea; + --system-gray6: #f2f2f7; + --label: #000000; + --secondary-label: rgb(60 60 67 / 60%); + --tertiary-label: rgb(60 60 67 / 30%); + --separator: rgb(60 60 67 / 29%); + --opaque-separator: #c6c6c8; + --grouped-background: #f2f2f7; + --grouped-surface: #ffffff; + --bar-background: rgb(249 249 249 / 94%); + + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-capsule: 999px; + + --control-height: 50px; + --control-height-md: 44px; + --control-height-sm: 34px; + --row-min-height: 44px; + --hairline: 0.5px; + + --shell-max: 960px; + --content-max: 720px; + --sidebar-width: 240px; + + --font-sans: "Inter", "SF Pro Text", "Segoe UI", sans-serif; + --font-mono: "Inter", ui-monospace, monospace; + + --shadow-none: none; + --z-header: 20; + --z-sidebar: 15; + --z-toast: 40; + + /* Backward-compatible names from the previous system UI */ + --color-ink: var(--label); + --color-ink-secondary: var(--secondary-label); + --color-muted: var(--secondary-label); + --color-line: var(--separator); + --color-line-strong: var(--opaque-separator); + --color-bg: var(--grouped-background); + --color-surface: var(--grouped-surface); + --color-accent: var(--accent); + --color-accent-hover: #0e6f4a; + --color-accent-soft: var(--accent-soft); + --color-warn: var(--system-orange); + --color-warn-bg: rgb(255 149 0 / 12%); + --color-danger: var(--system-red); + --color-danger-bg: rgb(255 59 48 / 12%); + --color-ok: var(--system-green); + --color-ok-bg: rgb(52 199 89 / 12%); +} + +@media (prefers-color-scheme: dark) { + :root { + --accent: #3cd68c; + --accent-soft: #14301f; + --system-red: #ff453a; + --system-orange: #ff9f0a; + --system-green: #30d158; + --system-gray3: #48484a; + --system-gray5: #2c2c2e; + --system-gray6: #1c1c1e; + --label: #ffffff; + --secondary-label: rgb(235 235 245 / 60%); + --tertiary-label: rgb(235 235 245 / 30%); + --separator: rgb(84 84 88 / 65%); + --opaque-separator: #38383a; + --grouped-background: #000000; + --grouped-surface: #1c1c1e; + --bar-background: rgb(29 29 29 / 94%); + --color-accent-hover: #5ee0a2; + } +} diff --git a/web/src/legal/CookieBanner.tsx b/web/src/legal/CookieBanner.tsx new file mode 100644 index 0000000..5092051 --- /dev/null +++ b/web/src/legal/CookieBanner.tsx @@ -0,0 +1,60 @@ +import { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import { AppButton, AppText } from "../ui"; +import { + readCookieChoice, + resetCookieChoice, + subscribeCookieChoice, + writeCookieChoice, +} from "./cookieConsent"; +import { LEGAL_PATHS } from "./operator"; + +export function CookieBanner() { + const [choice, setChoice] = useState(() => readCookieChoice()); + + useEffect(() => subscribeCookieChoice(() => setChoice(readCookieChoice())), []); + + if (choice) return null; + + return ( +
+ + Мы используем технические cookie, чтобы вы оставались в кабинете. Аналитические cookie + (Яндекс.Метрика) включаются только с вашего согласия.{" "} + + Подробнее + + +
+ writeCookieChoice("necessary")} + > + Только необходимые + + writeCookieChoice("all")}> + Принять все + +
+
+ ); +} + +export function CookieSettings() { + return ( +
+ { + resetCookieChoice(); + }} + > + Изменить выбор cookie + +
+ ); +} diff --git a/web/src/legal/LegalCheckboxes.tsx b/web/src/legal/LegalCheckboxes.tsx new file mode 100644 index 0000000..afa2309 --- /dev/null +++ b/web/src/legal/LegalCheckboxes.tsx @@ -0,0 +1,37 @@ +import { Link } from "react-router-dom"; +import { LEGAL_PATHS } from "./operator"; + +type Props = { + offer: boolean; + consent: boolean; + onOffer: (value: boolean) => void; + onConsent: (value: boolean) => void; +}; + +export function LegalCheckboxes({ offer, consent, onOffer, onConsent }: Props) { + return ( +
+ + +
+ ); +} diff --git a/web/src/legal/LegalPage.tsx b/web/src/legal/LegalPage.tsx new file mode 100644 index 0000000..2f4cfc6 --- /dev/null +++ b/web/src/legal/LegalPage.tsx @@ -0,0 +1,48 @@ +import { Link, Navigate, useParams } from "react-router-dom"; +import { AppText } from "../ui"; +import { CookieSettings } from "./CookieBanner"; +import { legalDocumentBySlug } from "./documents"; +import { SiteFooter } from "./SiteFooter"; + +export function LegalPage() { + const { slug } = useParams(); + const doc = legalDocumentBySlug(slug); + + if (!doc) { + return ; + } + + return ( +
+
+ + ← Назад + +
+ + {doc.title} + + {doc.lead} +
+ {doc.sections.map((section) => ( +
+

{section.heading}

+ {section.blocks.map((block, index) => + block.type === "ul" ? ( +
    + {block.items.map((item) => ( +
  • {item}
  • + ))} +
+ ) : ( +

{block.text}

+ ), + )} +
+ ))} + {doc.slug === "cookies" ? : null} +
+ +
+ ); +} diff --git a/web/src/legal/SiteFooter.tsx b/web/src/legal/SiteFooter.tsx new file mode 100644 index 0000000..f4dfccf --- /dev/null +++ b/web/src/legal/SiteFooter.tsx @@ -0,0 +1,32 @@ +import { Link } from "react-router-dom"; +import { APK_DOWNLOAD_HREF, APK_DOWNLOAD_NAME } from "../brand"; +import { LEGAL_PATHS, OPERATOR } from "./operator"; + +const LINKS = [ + { to: LEGAL_PATHS.offer, label: "Оферта" }, + { to: LEGAL_PATHS.privacy, label: "Конфиденциальность" }, + { to: LEGAL_PATHS.consent, label: "Согласие" }, + { to: LEGAL_PATHS.cookies, label: "Cookie" }, +] as const; + +export function SiteFooter() { + return ( + + ); +} diff --git a/web/src/legal/cookieConsent.ts b/web/src/legal/cookieConsent.ts new file mode 100644 index 0000000..cb5a610 --- /dev/null +++ b/web/src/legal/cookieConsent.ts @@ -0,0 +1,41 @@ +export const COOKIE_CONSENT_KEY = "ppm_cookie_consent"; +export type CookieChoice = "necessary" | "all"; + +const EVENT = "ppm-cookie-consent"; + +export function readCookieChoice(): CookieChoice | null { + try { + const raw = localStorage.getItem(COOKIE_CONSENT_KEY); + if (raw === "necessary" || raw === "all") return raw; + } catch { + /* private mode */ + } + return null; +} + +export function writeCookieChoice(choice: CookieChoice): void { + try { + localStorage.setItem(COOKIE_CONSENT_KEY, choice); + } catch { + /* ignore */ + } + window.dispatchEvent(new Event(EVENT)); +} + +export function resetCookieChoice(): void { + try { + localStorage.removeItem(COOKIE_CONSENT_KEY); + } catch { + /* ignore */ + } + window.dispatchEvent(new Event(EVENT)); +} + +export function subscribeCookieChoice(listener: () => void): () => void { + window.addEventListener(EVENT, listener); + window.addEventListener("storage", listener); + return () => { + window.removeEventListener(EVENT, listener); + window.removeEventListener("storage", listener); + }; +} diff --git a/web/src/legal/documents.ts b/web/src/legal/documents.ts new file mode 100644 index 0000000..974f10d --- /dev/null +++ b/web/src/legal/documents.ts @@ -0,0 +1,311 @@ +import { LEGAL_PATHS, OPERATOR } from "./operator"; + +export type LegalBlock = + | { type: "p"; text: string } + | { type: "ul"; items: string[] }; + +export type LegalSection = { + heading: string; + blocks: LegalBlock[]; +}; + +export type LegalDocument = { + slug: keyof typeof LEGAL_PATHS; + title: string; + lead: string; + sections: LegalSection[]; +}; + +const headerLines = [ + OPERATOR.name, + `ИНН: ${OPERATOR.inn}`, + `ОГРНИП: ${OPERATOR.ogrnip}`, + `Адрес: ${OPERATOR.address}`, + `Email для обращений: ${OPERATOR.email}`, +]; + +export const legalDocuments: LegalDocument[] = [ + { + slug: "offer", + title: "Пользовательское соглашение (публичная оферта)", + lead: `Настоящий документ является официальным предложением (${OPERATOR.shortName}) заключить договор на изложенных ниже условиях. Дата публикации: ${OPERATOR.updated}.`, + sections: [ + { + heading: "Реквизиты исполнителя", + blocks: [{ type: "ul", items: [...headerLines] }], + }, + { + heading: "1. Термины", + blocks: [ + { + type: "ul", + items: [ + "Пользователь — дееспособное физическое лицо, принявшее условия настоящей Оферты и использующее Сервис.", + `Сервис — веб-кабинет и связанные программные интерфейсы «${OPERATOR.serviceName}» по адресу ${OPERATOR.site}, предназначенные для учёта личных финансов.`, + "Приложение — мобильное приложение «Дожить до ЗП» для устройств на iOS и Android, предоставляющее доступ к тому же Сервису.", + "Тариф — выбранный Пользователем объём доступа: базовый функционал и, при наличии, платный расширенный доступ (PRO).", + "Оферта — настоящий документ.", + ], + }, + ], + }, + { + heading: "2. Предмет", + blocks: [ + { + type: "p", + text: "Исполнитель предоставляет Пользователю доступ к функционалу Сервиса и Приложения для самостоятельного ведения учёта личных финансов: бюджетов, расходов, графика выплат и связанных записей.", + }, + { + type: "p", + text: "Исполнитель не является кредитной, страховой, инвестиционной, платёжной или иной финансовой организацией, не привлекает денежные средства Пользователя, не открывает счета и не осуществляет переводы в пользу третьих лиц. Сервис носит исключительно информационно-учётный характер.", + }, + { + type: "p", + text: "Исполнитель не даёт инвестиционных, налоговых, бухгалтерских или иных профессиональных советов и не несёт ответственности за финансовые решения Пользователя, принятые на основании данных, расчётов или подсказок Сервиса.", + }, + ], + }, + { + heading: "3. Акцепт", + blocks: [ + { + type: "p", + text: "Регистрируясь или оплачивая доступ, Пользователь принимает условия настоящей Оферты. Акцептом также считается вход в Сервис или Приложение через авторизацию (в том числе через Яндекс ID) и начало использования функционала.", + }, + { + type: "p", + text: "Используя Сервис, Пользователь подтверждает, что ознакомился с Политикой конфиденциальности и дал согласие на обработку персональных данных в необходимом объёме.", + }, + ], + }, + { + heading: "4. Порядок оплаты", + blocks: [ + { + type: "p", + text: "Базовый функционал может предоставляться без оплаты. Оплата через ЮKassa (ООО НКО «ЮМани» и связанные платёжные сервисы) является платой за доступ к расширенному функционалу (Тариф PRO), если такой Тариф предложен в Сервисе или Приложении.", + }, + { + type: "p", + text: "Стоимость, срок и состав PRO указываются на экране оплаты до списания. Платёж считается совершённым после подтверждения ЮKassa. Исполнитель не получает и не хранит данные банковских карт: их обрабатывает ЮKassa.", + }, + ], + }, + { + heading: "5. Возврат", + blocks: [ + { + type: "p", + text: "Цифровая услуга по предоставлению доступа считается оказанной с момента активации соответствующего Тарифа в учётной записи Пользователя.", + }, + { + type: "p", + text: "Это не ограничивает права потребителя по Закону РФ от 07.02.1992 № 2300-1 «О защите прав потребителей». Пользователь вправе направить требование о возврате на email Исполнителя. Если доступ PRO не был активирован либо Пользователь не приступил к использованию оплаченного функционала, Исполнитель возвращает уплаченную сумму в полном объёме в срок, предусмотренный законом.", + }, + { + type: "p", + text: "Если доступ активирован и функционал использовался, Исполнитель рассматривает обращение индивидуально и может предложить возврат неиспользованной части периода либо отказать в возврате за фактически оказанную услугу — с указанием мотивов и с сохранением права Пользователя обратиться в уполномоченные органы.", + }, + ], + }, + { + heading: "6. Ответственность", + blocks: [ + { + type: "ul", + items: [ + "Сервис предоставляется «как есть». Исполнитель не гарантирует бесперебойную работу и абсолютную точность расчётов.", + "Исполнитель не отвечает за сохранность данных на устройстве Пользователя, утрату доступа к устройству, действия вредоносного ПО и последствия разглашения Пользователем своего токена или пароля Яндекс ID.", + "Исполнитель не отвечает за решения Пользователя о тратах, накоплениях, кредитах и инвестициях.", + "Совокупная ответственность Исполнителя по требованиям, связанным с платным доступом, ограничивается суммой, уплаченной Пользователем за соответствующий расчётный период, кроме случаев, когда иное прямо предусмотрено законом.", + ], + }, + ], + }, + { + heading: "7. Прочие условия", + blocks: [ + { + type: "p", + text: "Исполнитель вправе изменять Оферту, публикуя новую редакцию на этой странице. Продолжение использования Сервиса после публикации означает принятие новой редакции, если иное не требуется законом.", + }, + { + type: "p", + text: `Споры решаются путём переговоров, а при недостижении согласия — в порядке, установленном законодательством РФ. Контакт: ${OPERATOR.email}.`, + }, + ], + }, + ], + }, + { + slug: "privacy", + title: "Политика конфиденциальности", + lead: `Настоящая Политика описывает, какие персональные данные обрабатывает ${OPERATOR.shortName} при работе Сервиса «${OPERATOR.serviceName}». Дата публикации: ${OPERATOR.updated}.`, + sections: [ + { + heading: "Реквизиты оператора", + blocks: [{ type: "ul", items: [...headerLines] }], + }, + { + heading: "1. Оператор", + blocks: [ + { + type: "p", + text: `${OPERATOR.name} (ИНН ${OPERATOR.inn}, ОГРНИП ${OPERATOR.ogrnip}), адрес: ${OPERATOR.address}, email: ${OPERATOR.email}, является оператором персональных данных Пользователей Сервиса и Приложения.`, + }, + ], + }, + { + heading: "2. Какие данные собираются", + blocks: [ + { + type: "ul", + items: [ + "От Яндекса (OAuth / Яндекс ID): адрес электронной почты, отображаемое имя (если передано), ссылка на аватар (изображение).", + "Введённые Пользователем: данные о транзакциях и бюджетах (суммы, категории, комментарии, даты, параметры выплат).", + "Автоматически: файлы cookie, IP-адрес, тип и версия браузера или приложения, данные об устройстве, технические журналы запросов.", + ], + }, + ], + }, + { + heading: "3. Цели обработки", + blocks: [ + { + type: "ul", + items: [ + "Исполнение договора: предоставление доступа к Сервису, синхронизация данных между кабинетом и Приложением.", + "Связь с Пользователем: уведомления, ответы на обращения в поддержку.", + "Улучшение работы Сервиса: обезличенная аналитика сбоев и использования (при наличии согласия на аналитические cookie).", + ], + }, + ], + }, + { + heading: "4. Передача третьим лицам", + blocks: [ + { + type: "ul", + items: [ + "ЮKassa — для обработки платежей (идентификатор/email и сумма платежа).", + "Яндекс — для авторизации через Яндекс ID; Исполнитель получает от Яндекса указанные выше сведения профиля.", + ], + }, + { + type: "p", + text: "Данные банковских карт Исполнитель не получает и не хранит. Платёжные реквизиты обрабатывает ЮKassa.", + }, + ], + }, + { + heading: "5. Права Пользователя и сроки", + blocks: [ + { + type: "p", + text: `Пользователь вправе запросить доступ к своим данным, их уточнение, ограничение обработки, удаление аккаунта и отзыв согласия, направив письмо на ${OPERATOR.email}.`, + }, + { + type: "p", + text: "После удаления аккаунта персональные данные хранятся не дольше 3 лет — в объёме, необходимом для исполнения требований законодательства (в том числе о бухгалтерском учёте и защите прав потребителей), затем уничтожаются либо обезличиваются.", + }, + ], + }, + { + heading: "6. Cookie", + blocks: [ + { + type: "p", + text: "Сервис использует технические cookie для входа и сохранения сессии, а также — только после согласия — аналитические cookie (Яндекс.Метрика). Подробности и отказ от аналитики: Политика использования cookie.", + }, + ], + }, + ], + }, + { + slug: "consent", + title: "Согласие на обработку персональных данных", + lead: "Текст согласия, которое Пользователь даёт, отмечая соответствующий чек-бокс при регистрации (входе) в Сервисе или Приложении.", + sections: [ + { + heading: "Реквизиты оператора", + blocks: [{ type: "ul", items: [...headerLines] }], + }, + { + heading: "Текст согласия", + blocks: [ + { + type: "p", + text: `Я, Пользователь Сервиса «${OPERATOR.serviceName}» (ФИО или адрес электронной почты, полученные при авторизации через Яндекс ID либо указанные мной), даю согласие ${OPERATOR.shortName} (ИНН ${OPERATOR.inn}) на обработку моих персональных данных: адрес электронной почты, аватар (ссылка на изображение), данные о транзакциях и иных записях учёта, которые я ввожу.`, + }, + { + type: "p", + text: "Цели: предоставление доступа к Сервису, связь со мной, улучшение Сервиса.", + }, + { + type: "p", + text: "Способы обработки: сбор, запись, систематизация, хранение, уточнение, использование, передача (в случаях, указанных ниже), удаление — в том числе автоматизированно.", + }, + { + type: "p", + text: "Передача: ЮKassa (для платежей), Яндекс (для авторизации).", + }, + { + type: "p", + text: `Согласие действует до его отзыва. Отзыв направляется по email ${OPERATOR.email}. Отзыв не влияет на законность обработки, осуществлённой до его получения, и может сделать невозможным дальнейшее использование Сервиса.`, + }, + ], + }, + ], + }, + { + slug: "cookies", + title: "Политика использования cookie", + lead: `Документ описывает, какие cookie использует Сервис «${OPERATOR.serviceName}» и как отказаться от аналитических. Дата публикации: ${OPERATOR.updated}.`, + sections: [ + { + heading: "Реквизиты оператора", + blocks: [{ type: "ul", items: [...headerLines] }], + }, + { + heading: "1. Что такое cookie", + blocks: [ + { + type: "p", + text: "Cookie — небольшие файлы, которые сайт сохраняет в браузере. Они помогают запомнить вход и понять, как пользуются Сервисом.", + }, + ], + }, + { + heading: "2. Какие cookie мы используем", + blocks: [ + { + type: "ul", + items: [ + "Технические — обязательны для входа и работы кабинета (токен сессии, выбранная тема, выбор cookie). Без них авторизация невозможна.", + "Аналитические — Яндекс.Метрика: посещения страниц, источник перехода, тип устройства. Ставятся только после согласия «Принять все».", + "Рекламные cookie сейчас не используются.", + ], + }, + ], + }, + { + heading: "3. Как отказаться", + blocks: [ + { + type: "p", + text: "При первом заходе показывается баннер. Можно выбрать «Только необходимые» — аналитические cookie не устанавливаются. Выбор можно изменить кнопкой ниже или очистив данные сайта в браузере.", + }, + { + type: "p", + text: `Вопросы: ${OPERATOR.email}.`, + }, + ], + }, + ], + }, +]; + +export function legalDocumentBySlug(slug: string | undefined): LegalDocument | undefined { + return legalDocuments.find((doc) => doc.slug === slug); +} diff --git a/web/src/legal/legal.css b/web/src/legal/legal.css new file mode 100644 index 0000000..8c02a55 --- /dev/null +++ b/web/src/legal/legal.css @@ -0,0 +1,173 @@ +.legal-page { + min-height: 100vh; + display: flex; + flex-direction: column; + background: var(--grouped-background); +} + +.legal-page__inner { + width: min(720px, 100%); + margin: 0 auto; + padding: var(--space-6) var(--gutter) var(--space-8); + display: grid; + gap: var(--space-5); +} + +.legal-page__back { + color: var(--accent); + font-size: var(--text-subhead); + font-weight: 600; + width: fit-content; +} + +.legal-page__meta { + display: grid; + gap: var(--space-1); +} + +.legal-section { + display: grid; + gap: var(--space-3); +} + +.legal-section h2 { + font-size: var(--text-headline); +} + +.legal-section p, +.legal-section li { + font-size: var(--text-body); + line-height: 1.45; + color: var(--label); +} + +.legal-section ul { + margin: 0; + padding-left: 1.2em; + display: grid; + gap: var(--space-2); +} + +.legal-link { + color: var(--accent); + text-decoration: underline; + text-underline-offset: 2px; +} + +.site-footer { + margin-top: auto; + padding: var(--space-4) var(--gutter) calc(var(--space-5) + env(safe-area-inset-bottom, 0px)); + border-top: var(--hairline) solid var(--separator); + background: var(--bar-background); +} + +.site-footer__inner { + width: min(720px, 100%); + margin: 0 auto; + display: grid; + gap: var(--space-2); +} + +.site-footer__links { + display: flex; + flex-wrap: wrap; + gap: var(--space-2) var(--space-4); +} + +.site-footer__links a { + color: var(--secondary-label); + font-size: var(--text-footnote); +} + +.site-footer__links a:hover { + color: var(--accent); +} + +.site-footer__copy { + margin: 0; + color: var(--tertiary-label); + font-size: var(--text-caption1); +} + +.app-shell .site-footer { + margin-top: var(--space-4); + border-top: 0; + background: transparent; + padding: var(--space-3) var(--gutter) 0; +} + +.auth-panel__foot .site-footer { + margin: 0; + padding: 0; + border: 0; + background: transparent; +} + +.auth-panel__foot .site-footer__inner { + width: 100%; +} + +.app-shell .site-footer__inner { + width: 100%; +} + +.legal-check { + display: grid; + grid-template-columns: 22px 1fr; + gap: var(--space-2); + align-items: start; + margin: 0; + color: var(--secondary-label); + font-size: var(--text-footnote); + line-height: 1.4; + cursor: pointer; +} + +.legal-check input { + margin-top: 2px; + accent-color: var(--accent); +} + +.legal-checks { + display: grid; + gap: var(--space-3); +} + +.cookie-banner { + position: fixed; + left: var(--gutter); + right: var(--gutter); + bottom: calc(var(--gutter) + env(safe-area-inset-bottom, 0px)); + z-index: 50; + max-width: 560px; + margin: 0 auto; + padding: var(--space-4); + border-radius: var(--radius-lg); + background: var(--grouped-surface); + box-shadow: 0 12px 40px rgb(0 0 0 / 18%); + display: grid; + gap: var(--space-3); +} + +.cookie-banner__actions { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.btn--yandex.is-disabled, +.btn--yandex[aria-disabled="true"] { + opacity: 0.45; + pointer-events: none; +} + +@media (max-width: 860px) { + .app-shell .site-footer { + padding-inline: 0; + } + + .app-shell .site-footer__links { + flex-wrap: nowrap; + overflow-x: auto; + } +} diff --git a/web/src/legal/operator.ts b/web/src/legal/operator.ts new file mode 100644 index 0000000..16907e2 --- /dev/null +++ b/web/src/legal/operator.ts @@ -0,0 +1,20 @@ +export const OPERATOR = { + name: "Индивидуальный предприниматель Архангельский Владимир Александрович", + shortName: "ИП Архангельский В.А.", + inn: "772270222393", + ogrnip: "323774600438338", + address: "105037, г. Москва, ул. 2-я Парковая, д. 16, кв. 8", + email: "hohnergold@yandex.ru", + serviceName: "Дожить до ЗП", + site: "https://please-pay-me.ru", + updated: "20 сентября 2026 г.", +} as const; + +export const LEGAL_PATHS = { + offer: "/legal/offer", + privacy: "/legal/privacy", + consent: "/legal/consent", + cookies: "/legal/cookies", +} as const; + +export type LegalSlug = keyof typeof LEGAL_PATHS; diff --git a/web/src/lib/calendarColors.ts b/web/src/lib/calendarColors.ts new file mode 100644 index 0000000..1865cef --- /dev/null +++ b/web/src/lib/calendarColors.ts @@ -0,0 +1,36 @@ +/** Stable pastel palette for calendar layers (budgets / jobs). */ + +const BUDGET_PALETTE = [ + { fill: "rgba(14, 107, 69, 0.28)", solid: "#0e6b45", label: "зелёный" }, + { fill: "rgba(30, 90, 140, 0.28)", solid: "#1e5a8c", label: "синий" }, + { fill: "rgba(168, 90, 28, 0.28)", solid: "#a85a1c", label: "янтарный" }, + { fill: "rgba(140, 50, 70, 0.28)", solid: "#8c3246", label: "бордовый" }, + { fill: "rgba(70, 110, 40, 0.28)", solid: "#466e28", label: "оливковый" }, + { fill: "rgba(40, 120, 120, 0.28)", solid: "#287878", label: "бирюза" }, + { fill: "rgba(110, 70, 130, 0.22)", solid: "#6e4682", label: "слива" }, + { fill: "rgba(90, 90, 50, 0.28)", solid: "#5a5a32", label: "хаки" }, +] as const; + +const JOB_PALETTE = [ + { solid: "#c45c00", soft: "rgba(196, 92, 0, 0.15)" }, + { solid: "#b00040", soft: "rgba(176, 0, 64, 0.12)" }, + { solid: "#005f8a", soft: "rgba(0, 95, 138, 0.12)" }, + { solid: "#5a3d00", soft: "rgba(90, 61, 0, 0.12)" }, + { solid: "#00664d", soft: "rgba(0, 102, 77, 0.12)" }, +] as const; + +function hashId(id: number): number { + let x = id | 0; + x = ((x >>> 16) ^ x) * 0x45d9f3b; + x = ((x >>> 16) ^ x) * 0x45d9f3b; + x = (x >>> 16) ^ x; + return Math.abs(x); +} + +export function budgetColor(id: number) { + return BUDGET_PALETTE[hashId(id) % BUDGET_PALETTE.length]!; +} + +export function jobColor(id: number) { + return JOB_PALETTE[hashId(id) % JOB_PALETTE.length]!; +} diff --git a/web/src/lib/date.ts b/web/src/lib/date.ts new file mode 100644 index 0000000..733d80d --- /dev/null +++ b/web/src/lib/date.ts @@ -0,0 +1,7 @@ +export function todayIso(): string { + const d = new Date(); + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${y}-${m}-${day}`; +} diff --git a/web/src/lib/paySchedule.ts b/web/src/lib/paySchedule.ts new file mode 100644 index 0000000..f28e045 --- /dev/null +++ b/web/src/lib/paySchedule.ts @@ -0,0 +1,138 @@ +/** Mirrors PleasePayMe.Application.Jobs.PaySchedule for calendar rendering. */ + +export type WeekendPolicy = "before_weekend" | "after_weekend"; + +export type PayOccurrence = { + date: string; + jobId: number; + jobName: string; + scheduledDay: number; + percent: number; + amount: number; +}; + +function pad2(n: number): string { + return String(n).padStart(2, "0"); +} + +export function toIso(year: number, month: number, day: number): string { + return `${year}-${pad2(month)}-${pad2(day)}`; +} + +export function parseIso(iso: string): { year: number; month: number; day: number } { + const [year, month, day] = iso.slice(0, 10).split("-").map(Number); + return { year, month, day }; +} + +export function addDaysIso(iso: string, delta: number): string { + const { year, month, day } = parseIso(iso); + const dt = new Date(Date.UTC(year, month - 1, day + delta)); + return toIso(dt.getUTCFullYear(), dt.getUTCMonth() + 1, dt.getUTCDate()); +} + +export function weekdayMon0(iso: string): number { + const { year, month, day } = parseIso(iso); + // 0 = Mon … 6 = Sun + const js = new Date(Date.UTC(year, month - 1, day)).getUTCDay(); + return (js + 6) % 7; +} + +export function adjustForWeekend(iso: string, policy: WeekendPolicy): string { + const wd = weekdayMon0(iso); + // Mon=0 … Sat=5, Sun=6 + if (wd === 5) { + return policy === "before_weekend" ? addDaysIso(iso, -1) : addDaysIso(iso, 2); + } + if (wd === 6) { + return policy === "before_weekend" ? addDaysIso(iso, -2) : addDaysIso(iso, 1); + } + return iso; +} + +export function nominalDate(year: number, month: number, dayOfMonth: number): string { + const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate(); + const actualDay = Math.min(dayOfMonth, daysInMonth); + return toIso(year, month, actualDay); +} + +export function paysInRange( + jobs: Array<{ + id: number; + name: string; + salary_amount: number; + pay_days: number[]; + first_pay_percent: number; + weekend_policy: WeekendPolicy; + is_active: boolean; + }>, + rangeStart: string, + rangeEnd: string, +): PayOccurrence[] { + const start = parseIso(rangeStart); + const end = parseIso(rangeEnd); + const result: PayOccurrence[] = []; + + for (const job of jobs) { + if (!job.is_active || job.pay_days.length === 0) continue; + + const ordered = [...job.pay_days].sort((a, b) => a - b).slice(0, 2); + const firstPercent = ordered.length <= 1 ? 100 : job.first_pay_percent; + const secondPercent = ordered.length === 1 ? 0 : 100 - firstPercent; + + // Extra months: weekend shift can move pay across month boundary. + let cursor = new Date(Date.UTC(start.year, start.month - 2, 1)); + const last = new Date(Date.UTC(end.year, end.month, 1)); + + while (cursor <= last) { + const year = cursor.getUTCFullYear(); + const month = cursor.getUTCMonth() + 1; + + ordered.forEach((scheduledDay, i) => { + const nominal = nominalDate(year, month, scheduledDay); + const actual = adjustForWeekend(nominal, job.weekend_policy); + if (actual < rangeStart || actual > rangeEnd) return; + + const percent = i === 0 ? firstPercent : secondPercent; + const amount = Math.round((job.salary_amount * percent) / 100 * 100) / 100; + result.push({ + date: actual, + jobId: job.id, + jobName: job.name, + scheduledDay, + percent, + amount, + }); + }); + + cursor = new Date(Date.UTC(year, month, 1)); + } + } + + return result.sort((a, b) => + a.date === b.date + ? a.jobId - b.jobId || a.scheduledDay - b.scheduledDay + : a.date < b.date + ? -1 + : 1, + ); +} + +export function monthGrid(year: number, month: number): string[] { + const first = toIso(year, month, 1); + const startOffset = weekdayMon0(first); + const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate(); + const cells: string[] = []; + + for (let i = 0; i < startOffset; i++) { + cells.push(addDaysIso(first, i - startOffset)); + } + for (let d = 1; d <= daysInMonth; d++) { + cells.push(toIso(year, month, d)); + } + while (cells.length % 7 !== 0 || cells.length < 35) { + const last = cells[cells.length - 1]!; + cells.push(addDaysIso(last, 1)); + if (cells.length >= 42) break; + } + return cells; +} diff --git a/web/src/main.tsx b/web/src/main.tsx new file mode 100644 index 0000000..efbf46d --- /dev/null +++ b/web/src/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; +import App from "./App"; +import "./styles.css"; + +createRoot(document.getElementById("root")!).render( + + + + + , +); diff --git a/web/src/pages/BudgetsPage.tsx b/web/src/pages/BudgetsPage.tsx new file mode 100644 index 0000000..713d39c --- /dev/null +++ b/web/src/pages/BudgetsPage.tsx @@ -0,0 +1,204 @@ +import { useId } from "react"; +import { useCabinet } from "../cabinet/CabinetContext"; +import { formatDate, formatMoney } from "../api"; +import { todayIso } from "../lib/date"; +import { Button, Field, PageHeader, Section } from "../components/ui"; + +export function BudgetsPage() { + const nameId = useId(); + const amountId = useId(); + const startId = useId(); + const endId = useId(); + const { + budgets, + saving, + noBudget, + selectBudget, + toggleBudgetActive, + deleteBudget, + budgetName, + setBudgetName, + budgetAmount, + setBudgetAmount, + budgetStart, + setBudgetStart, + budgetEnd, + setBudgetEnd, + resetExpenses, + setResetExpenses, + createMode, + setCreateMode, + onSaveBudget, + selectedBudgetId, + } = useCabinet(); + + const beginCreate = () => { + setCreateMode(true); + setBudgetName("Бюджет"); + setBudgetAmount(""); + setBudgetStart(todayIso()); + setBudgetEnd(""); + setResetExpenses(false); + }; + + return ( + <> + + +
+ Новый + + } + > + {budgets.length === 0 ? ( +

Создай первый бюджет формой ниже.

+ ) : ( +
    + {budgets.map((item) => { + const b = item.budget; + const isSelected = b.id === selectedBudgetId || item.selected; + return ( +
  • +
    + + {b.name} + {isSelected ? " · текущий" : ""} + {!b.is_active ? " · выкл" : ""} + + + {formatMoney(b.total_amount)} · {formatDate(b.start_date)}– + {formatDate(b.end_date)} · остаток {formatMoney(item.remaining)} + +
    +
    + + + +
    +
  • + ); + })} +
+ )} +
+ +
+
void onSaveBudget(e)}> + setBudgetName(e.target.value)} + placeholder="Зарплата" + disabled={saving} + /> + setBudgetAmount(e.target.value)} + placeholder="25000" + required + disabled={saving} + /> +
+ setBudgetStart(e.target.value)} + required + disabled={saving} + /> + setBudgetEnd(e.target.value)} + required + disabled={saving} + /> +
+ {!createMode && !noBudget ? ( + + ) : null} +
+ + {!createMode && !noBudget ? ( + + ) : null} + {createMode && !noBudget ? ( + + ) : null} +
+ +
+ + ); +} diff --git a/web/src/pages/CalendarPage.tsx b/web/src/pages/CalendarPage.tsx new file mode 100644 index 0000000..39c6081 --- /dev/null +++ b/web/src/pages/CalendarPage.tsx @@ -0,0 +1,505 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { Link } from "react-router-dom"; +import { + fetchMyExpensesRange, + fetchMyJobs, + formatDate, + formatMoney, +} from "../api"; +import { useCabinet } from "../cabinet/CabinetContext"; +import { Button, EmptyState, Flash, PageHeader, Section } from "../components/ui"; +import { budgetColor, jobColor } from "../lib/calendarColors"; +import { todayIso } from "../lib/date"; +import { + monthGrid, + parseIso, + paysInRange, + type PayOccurrence, +} from "../lib/paySchedule"; +import type { Expense, Job } from "../types"; + +const WEEKDAYS = ["пн", "вт", "ср", "чт", "пт", "сб", "вс"]; + +function monthTitle(year: number, month: number): string { + const raw = new Date(Date.UTC(year, month - 1, 1)).toLocaleDateString("ru-RU", { + month: "long", + year: "numeric", + timeZone: "UTC", + }); + return raw.charAt(0).toUpperCase() + raw.slice(1); +} + +function shiftMonth(year: number, month: number, delta: number): { year: number; month: number } { + const d = new Date(Date.UTC(year, month - 1 + delta, 1)); + return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1 }; +} + +/** Compact sum for calendar cells: 350 / 1,2 тыс / 12 тыс */ +function compactSpend(amount: number): string { + if (amount < 1000) { + return amount % 1 === 0 + ? String(amount) + : amount.toLocaleString("ru-RU", { maximumFractionDigits: 0 }); + } + const thousands = amount / 1000; + const formatted = + thousands >= 10 + ? thousands.toLocaleString("ru-RU", { maximumFractionDigits: 0 }) + : thousands.toLocaleString("ru-RU", { + minimumFractionDigits: 0, + maximumFractionDigits: 1, + }); + return `${formatted} тыс`; +} + +type DaySpend = { total: number; count: number; items: Expense[] }; + +export function CalendarPage() { + const { budgets, loading: budgetsLoading } = useCabinet(); + const today = todayIso(); + const initial = parseIso(today); + + const [year, setYear] = useState(initial.year); + const [month, setMonth] = useState(initial.month); + const [jobs, setJobs] = useState([]); + const [expenses, setExpenses] = useState([]); + const [jobsLoading, setJobsLoading] = useState(true); + const [expensesLoading, setExpensesLoading] = useState(true); + const [error, setError] = useState(null); + const [selected, setSelected] = useState(today); + + const cells = useMemo(() => monthGrid(year, month), [year, month]); + const rangeStart = cells[0]!; + const rangeEnd = cells[cells.length - 1]!; + + const budgetNameById = useMemo(() => { + const map = new Map(); + for (const item of budgets) { + map.set(item.budget.id, item.budget.name); + } + return map; + }, [budgets]); + + const reloadJobs = useCallback(async () => { + setJobsLoading(true); + try { + const data = await fetchMyJobs(); + setJobs(data.items); + } catch (err) { + setError(err instanceof Error ? err.message : "Не удалось загрузить работы"); + } finally { + setJobsLoading(false); + } + }, []); + + const reloadExpenses = useCallback(async () => { + setExpensesLoading(true); + try { + const data = await fetchMyExpensesRange(rangeStart, rangeEnd); + setExpenses(data.items); + } catch (err) { + setError(err instanceof Error ? err.message : "Не удалось загрузить траты"); + setExpenses([]); + } finally { + setExpensesLoading(false); + } + }, [rangeStart, rangeEnd]); + + useEffect(() => { + void reloadJobs(); + }, [reloadJobs]); + + useEffect(() => { + void reloadExpenses(); + }, [reloadExpenses]); + + const pays = useMemo( + () => paysInRange(jobs, rangeStart, rangeEnd), + [jobs, rangeStart, rangeEnd], + ); + + const paysByDate = useMemo(() => { + const map = new Map(); + for (const pay of pays) { + const list = map.get(pay.date) ?? []; + list.push(pay); + map.set(pay.date, list); + } + return map; + }, [pays]); + + const spendByDate = useMemo(() => { + const map = new Map(); + for (const exp of expenses) { + const key = exp.spent_at.slice(0, 10); + const cur = map.get(key) ?? { total: 0, count: 0, items: [] }; + cur.total += exp.amount; + cur.count += 1; + cur.items.push(exp); + map.set(key, cur); + } + return map; + }, [expenses]); + + const selectedBudgets = useMemo(() => { + if (!selected) return []; + return budgets.filter( + (item) => + item.budget.start_date <= selected && selected <= item.budget.end_date, + ); + }, [budgets, selected]); + + const selectedPays = selected ? (paysByDate.get(selected) ?? []) : []; + const selectedSpend = selected ? spendByDate.get(selected) : undefined; + const selectedExpenses = selectedSpend?.items ?? []; + + const loading = budgetsLoading || jobsLoading || expensesLoading; + const empty = + !loading && + budgets.length === 0 && + jobs.filter((j) => j.is_active).length === 0 && + expenses.length === 0; + + const dayHasContent = + selectedBudgets.length > 0 || + selectedPays.length > 0 || + selectedExpenses.length > 0; + + return ( + <> + + + + +
+ + + +
+ } + > + {loading ? ( +

Загрузка…

+ ) : empty ? ( + + Пока нечего показывать. Задай бюджет,{" "} + работу или{" "} + трату. + + ) : ( +
+
+ {WEEKDAYS.map((d) => ( + + {d} + + ))} +
+
+ {cells.map((iso) => { + const { month: cellMonth, day } = parseIso(iso); + const inMonth = cellMonth === month; + const isToday = iso === today; + const isSelected = iso === selected; + const dayBudgets = budgets.filter( + (item) => + item.budget.start_date <= iso && iso <= item.budget.end_date, + ); + const dayPays = paysByDate.get(iso) ?? []; + const daySpend = spendByDate.get(iso); + const titleParts = [ + ...dayBudgets.map((b) => `Бюджет: ${b.budget.name}`), + ...dayPays.map( + (p) => + `ЗП ${p.jobName}: ${formatMoney(p.amount)} (${p.percent}%)`, + ), + ]; + if (daySpend) { + titleParts.push( + `Траты: ${formatMoney(daySpend.total)} · ${daySpend.count} шт.`, + ); + } + + return ( + + ); + })} +
+
+ )} + + + {!loading && !empty ? ( + <> +
+
    + {budgets.map((item) => { + const c = budgetColor(item.budget.id); + return ( +
  • + + + {item.budget.name} + {!item.budget.is_active ? " · выкл" : ""} + + {" "} + · {formatDate(item.budget.start_date)}– + {formatDate(item.budget.end_date)} + + +
  • + ); + })} + {jobs + .filter((j) => j.is_active) + .map((job) => { + const c = jobColor(job.id); + return ( +
  • + + + ЗП · {job.name} + + {" "} + · дни {job.pay_days.join(", ")} + + +
  • + ); + })} +
  • + + + Траты + · точка и сумма в ячейке + +
  • +
+
+ + {selected ? ( +
+ {!dayHasContent ? ( +

Нет бюджетов, выплат и трат в этот день.

+ ) : ( + <> + {selectedExpenses.length > 0 ? ( +
+

+ Операции · {selectedExpenses.length} +

+
    + {selectedExpenses.map((exp) => { + const c = budgetColor(exp.budget_id); + const name = + budgetNameById.get(exp.budget_id) ?? `бюджет #${exp.budget_id}`; + return ( +
  • +
    + + {formatMoney(exp.amount)} + {exp.note ? ` · ${exp.note}` : ""} + + + + {name} + +
    +
  • + ); + })} +
+
+ ) : null} + + {selectedBudgets.length > 0 || selectedPays.length > 0 ? ( +
+ {(selectedBudgets.length > 0 || selectedPays.length > 0) && + selectedExpenses.length > 0 ? ( +

Контекст дня

+ ) : null} +
    + {selectedBudgets.map((item) => { + const c = budgetColor(item.budget.id); + return ( +
  • +
    + + + {item.budget.name} + + + Бюджет · остаток {formatMoney(item.remaining)} + +
    +
  • + ); + })} + {selectedPays.map((p) => { + const c = jobColor(p.jobId); + return ( +
  • +
    + + + {p.jobName} + + + Выплата · {formatMoney(p.amount)} · {p.percent}% · день{" "} + {p.scheduledDay} + +
    +
  • + ); + })} +
+
+ ) : null} + + )} +
+ ) : null} + + ) : null} + + ); +} diff --git a/web/src/pages/ComingSoonPage.tsx b/web/src/pages/ComingSoonPage.tsx new file mode 100644 index 0000000..4615088 --- /dev/null +++ b/web/src/pages/ComingSoonPage.tsx @@ -0,0 +1,22 @@ +import { PageHeader } from "../components/ui"; + +type Props = { + title: string; + description: string; +}; + +/** Placeholder route for modules not yet shipped — keeps IA expandable. */ +export function ComingSoonPage({ title, description }: Props) { + return ( + <> + +
+

Скоро

+

+ Раздел зарезервирован в навигации. Когда появится функционал — сюда + подключится страница без перестройки оболочки. +

+
+ + ); +} diff --git a/web/src/pages/JournalPage.tsx b/web/src/pages/JournalPage.tsx new file mode 100644 index 0000000..679fc47 --- /dev/null +++ b/web/src/pages/JournalPage.tsx @@ -0,0 +1,97 @@ +import { Link } from "react-router-dom"; +import { formatDate, formatMoney } from "../api"; +import { useCabinet } from "../cabinet/CabinetContext"; +import { Button, EmptyState, PageHeader } from "../components/ui"; +import { AppListSection, AppListTile, AppSegmented } from "../ui"; + +export function JournalPage() { + const { + budgets, + expenses, + noBudget, + page, + setPage, + loading, + journalScope, + setJournalScope, + status, + } = useCabinet(); + + const budgetName = (id: number) => + budgets.find((b) => b.budget.id === id)?.budget.name ?? `бюджет #${id}`; + + return ( + <> + + + {noBudget ? ( + + Нет бюджетов — создай в разделе Бюджеты. + + ) : !expenses && loading ? ( + Загрузка… + ) : !expenses ? ( + Не удалось загрузить журнал. + ) : ( + <> +
+ +
+ + {expenses.items.length === 0 ? ( + + Записей нет. Добавь расход в Операциях. + + ) : ( + + {expenses.items.map((item) => ( + + ))} + + )} + + {expenses.total_pages > 1 && ( +
+ + + {page + 1} / {expenses.total_pages} + + +
+ )} + + )} + + ); +} diff --git a/web/src/pages/LoginPage.tsx b/web/src/pages/LoginPage.tsx new file mode 100644 index 0000000..f71ce91 --- /dev/null +++ b/web/src/pages/LoginPage.tsx @@ -0,0 +1,115 @@ +import { useEffect, useState } from "react"; +import { Navigate, useLocation, useNavigate } from "react-router-dom"; +import { AuthLayout } from "../components/layout/AuthLayout"; +import { Flash } from "../components/ui"; +import { APK_DOWNLOAD_HREF, APK_DOWNLOAD_NAME } from "../brand"; +import { LegalCheckboxes } from "../legal/LegalCheckboxes"; +import { SiteFooter } from "../legal/SiteFooter"; +import { YandexLoginButton } from "../YandexLoginButton"; +import { yandexCodeFromQuery, yandexErrorFromQuery, yandexRedirectUri } from "../auth/yandexRedirect"; +import { captureTelegramLinkToken, peekTelegramLinkToken } from "../auth/telegramLink"; +import { useAuth } from "../auth/AuthContext"; +import { fetchAuthProviders } from "../api"; + +export function LoginPage() { + const { user, busy, error, loginYandex } = useAuth(); + const location = useLocation(); + const navigate = useNavigate(); + const [yandexClientId, setYandexClientId] = useState(null); + const [configuredRedirect, setConfiguredRedirect] = useState(null); + const [oauthError, setOauthError] = useState(null); + const [offerAccepted, setOfferAccepted] = useState(false); + const [consentAccepted, setConsentAccepted] = useState(false); + const redirectUri = yandexRedirectUri(configuredRedirect); + const accepted = offerAccepted && consentAccepted; + const tgLink = peekTelegramLinkToken() ?? captureTelegramLinkToken(location.search); + + useEffect(() => { + captureTelegramLinkToken(location.search); + }, [location.search]); + + useEffect(() => { + let cancelled = false; + void fetchAuthProviders() + .then((providers) => { + if (cancelled) return; + const yandex = providers.yandex; + const id = yandex?.enabled ? yandex.client_id : null; + setYandexClientId(id && id.length > 0 ? id : null); + setConfiguredRedirect(yandex?.redirect_uri ?? null); + }) + .catch(() => { + if (!cancelled) { + setYandexClientId(null); + setConfiguredRedirect(null); + } + }); + return () => { + cancelled = true; + }; + }, []); + + useEffect(() => { + const denied = yandexErrorFromQuery(location.search); + if (denied) { + setOauthError(denied); + navigate("/login", { replace: true }); + return; + } + + const code = yandexCodeFromQuery(location.search); + if (!code) return; + + void loginYandex(code, redirectUri).finally(() => { + navigate("/login", { replace: true }); + }); + }, [location.search, loginYandex, navigate, redirectUri]); + + if (user) { + return ; + } + + return ( + Вход…

+ ) : ( + <> + {yandexClientId ? ( + <> + + + + ) : null} + + Скачать для Android + + + ) + } + flash={} + footer={} + /> + ); +} diff --git a/web/src/pages/OperationsPage.tsx b/web/src/pages/OperationsPage.tsx new file mode 100644 index 0000000..ff9adf9 --- /dev/null +++ b/web/src/pages/OperationsPage.tsx @@ -0,0 +1,118 @@ +import { useId, useMemo } from "react"; +import { Link } from "react-router-dom"; +import { useCabinet } from "../cabinet/CabinetContext"; +import { Button, Field, PageHeader, Section } from "../components/ui"; + +export function OperationsPage() { + const amountId = useId(); + const noteId = useId(); + const dateId = useId(); + const budgetId = useId(); + const { + budgets, + amount, + setAmount, + note, + setNote, + spentAt, + setSpentAt, + expenseBudgetId, + setExpenseBudgetId, + saving, + noBudget, + onAddExpense, + onUndo, + } = useCabinet(); + + const selectedExpenseBudget = useMemo( + () => budgets.find((b) => b.budget.id === expenseBudgetId)?.budget, + [budgets, expenseBudgetId], + ); + + return ( + <> + + + {noBudget ? ( +

+ Сначала создай бюджет на странице{" "} + Бюджеты. +

+ ) : ( +
+
void onAddExpense(e)}> +
+ + + {selectedExpenseBudget && !selectedExpenseBudget.is_active ? ( +

+ Бюджет выключен — трата всё равно запишется (даты периода не + ограничивают). +

+ ) : null} +
+
+ setAmount(e.target.value)} + placeholder="250" + required + disabled={saving} + /> + setSpentAt(e.target.value)} + disabled={saving} + /> +
+ setNote(e.target.value)} + placeholder="кофе, обед, такси…" + disabled={saving} + /> +
+ + +
+ +
+ )} + + ); +} diff --git a/web/src/pages/OverviewPage.tsx b/web/src/pages/OverviewPage.tsx new file mode 100644 index 0000000..bec2241 --- /dev/null +++ b/web/src/pages/OverviewPage.tsx @@ -0,0 +1,112 @@ +import { Link } from "react-router-dom"; +import { formatDate, formatMoney } from "../api"; +import { useCabinet } from "../cabinet/CabinetContext"; +import { Banner, PageHeader } from "../components/ui"; +import { + AppListSection, + AppListTile, + AppProgress, + AppSkeleton, + AppText, +} from "../ui"; + +function SkeletonOverview() { + return ( +
+ +
+ +
+ + {Array.from({ length: 4 }).map((_, i) => ( + } value={} /> + ))} + +
+ ); +} + +export function OverviewPage() { + const { status, loading, noBudget } = useCabinet(); + + if (loading && !status && !noBudget) { + return ; + } + + if (noBudget) { + return ( + <> + + + Перейти к бюджетам + + + ); + } + + if (!status) { + return ( + + ); + } + + return ( + <> + + Лимит на сегодня {formatMoney(status.daily_limit)} + {status.spent_today > 0 + ? ` · израсходовано ${formatMoney(status.spent_today)}` + : ""} + + } + /> + + Остаток + + {formatMoney(status.remaining)} + + +
+ +
+ + + + + + + {formatMoney(status.remaining_today)} + + } + /> + + + {(status.is_over_budget || status.is_over_daily || status.is_expired) && ( + + {status.is_expired + ? "Период закончился — задай новый бюджет." + : status.is_over_budget + ? "Бюджет превышен." + : "Сегодняшний лимит превышен — завтра пересчитается."} + + )} + + ); +} diff --git a/web/src/pages/PeriodPage.tsx b/web/src/pages/PeriodPage.tsx new file mode 100644 index 0000000..805af87 --- /dev/null +++ b/web/src/pages/PeriodPage.tsx @@ -0,0 +1,114 @@ +import { useId } from "react"; +import { Link } from "react-router-dom"; +import { useCabinet } from "../cabinet/CabinetContext"; +import { Button, Field, PageHeader, Section } from "../components/ui"; + +/** Period editor for the current budget. */ +export function PeriodPage() { + const nameId = useId(); + const budgetAmountId = useId(); + const budgetStartId = useId(); + const budgetEndId = useId(); + const { + budgetName, + setBudgetName, + budgetAmount, + setBudgetAmount, + budgetStart, + setBudgetStart, + budgetEnd, + setBudgetEnd, + resetExpenses, + setResetExpenses, + saving, + noBudget, + createMode, + setCreateMode, + onSaveBudget, + status, + } = useCabinet(); + + return ( + <> + + Редактирование текущего бюджета. Список и переключение — в разделе{" "} + Бюджеты. + + } + /> + +
+
void onSaveBudget(e)}> + setBudgetName(e.target.value)} + disabled={saving} + /> + setBudgetAmount(e.target.value)} + placeholder="25000" + required + disabled={saving} + /> +
+ setBudgetStart(e.target.value)} + required + disabled={saving} + /> + setBudgetEnd(e.target.value)} + required + disabled={saving} + /> +
+ {!createMode && !noBudget ? ( + + ) : null} +
+ +
+ +
+ + ); +} diff --git a/web/src/pages/WorkPage.tsx b/web/src/pages/WorkPage.tsx new file mode 100644 index 0000000..57a57c1 --- /dev/null +++ b/web/src/pages/WorkPage.tsx @@ -0,0 +1,404 @@ +import { useCallback, useEffect, useId, useMemo, useState, type FormEvent } from "react"; +import { + createMyJob, + deleteMyJob, + fetchMyJobs, + formatDate, + formatMoney, + updateMyJob, +} from "../api"; +import { Button, EmptyState, Field, Flash, PageHeader, Section } from "../components/ui"; +import type { Job } from "../types"; + +const QUICK_DAYS = [1, 5, 10, 15, 20, 25, 28]; + +function parsePayDays(raw: string): number[] { + const parts = raw + .split(/[,;\s]+/) + .map((p) => p.trim()) + .filter(Boolean); + const days = parts + .map((p) => Number(p)) + .filter((n) => Number.isInteger(n) && n >= 1 && n <= 31); + return [...new Set(days)].sort((a, b) => a - b).slice(0, 2); +} + +function formatPayDays(days: number[]): string { + return days.join(", "); +} + +function weekendLabel(policy: Job["weekend_policy"]): string { + return policy === "after_weekend" ? "после выходных" : "до выходных"; +} + +export function WorkPage() { + const nameId = useId(); + const salaryId = useId(); + const daysId = useId(); + const percentId = useId(); + + const [jobs, setJobs] = useState([]); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + const [notice, setNotice] = useState(null); + + const [editingId, setEditingId] = useState(null); + const [name, setName] = useState(""); + const [salary, setSalary] = useState(""); + const [payDaysRaw, setPayDaysRaw] = useState("5, 20"); + const [firstPercent, setFirstPercent] = useState("40"); + const [weekendPolicy, setWeekendPolicy] = useState<"before_weekend" | "after_weekend">( + "before_weekend", + ); + const [isActive, setIsActive] = useState(true); + + const selectedDays = useMemo(() => parsePayDays(payDaysRaw), [payDaysRaw]); + const secondPercent = Math.max(0, 100 - Number(firstPercent.replace(",", ".") || 0)); + + const reload = useCallback(async () => { + setLoading(true); + setError(null); + try { + const data = await fetchMyJobs(); + setJobs(data.items); + } catch (err) { + setError(err instanceof Error ? err.message : "Ошибка загрузки"); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + void reload(); + }, [reload]); + + useEffect(() => { + if (!notice) return; + const t = window.setTimeout(() => setNotice(null), 3200); + return () => window.clearTimeout(t); + }, [notice]); + + function resetForm() { + setEditingId(null); + setName(""); + setSalary(""); + setPayDaysRaw("5, 20"); + setFirstPercent("40"); + setWeekendPolicy("before_weekend"); + setIsActive(true); + } + + function startEdit(job: Job) { + setEditingId(job.id); + setName(job.name); + setSalary(String(job.salary_amount)); + setPayDaysRaw(formatPayDays(job.pay_days)); + setFirstPercent(String(job.first_pay_percent)); + setWeekendPolicy(job.weekend_policy); + setIsActive(job.is_active); + } + + function toggleQuickDay(day: number) { + const current = parsePayDays(payDaysRaw); + let next: number[]; + if (current.includes(day)) { + next = current.filter((d) => d !== day); + } else if (current.length >= 2) { + setError("Можно выбрать максимум 2 дня выплаты"); + return; + } else { + next = [...current, day].sort((a, b) => a - b); + } + setError(null); + setPayDaysRaw(formatPayDays(next)); + } + + async function onSubmit(event: FormEvent) { + event.preventDefault(); + const amount = Number(salary.replace(",", ".")); + const payDays = parsePayDays(payDaysRaw); + const percent = Number(firstPercent.replace(",", ".")); + if (!name.trim()) { + setError("Укажи название работы"); + return; + } + if (!Number.isFinite(amount) || amount <= 0) { + setError("Зарплата должна быть больше нуля"); + return; + } + if (payDays.length === 0) { + setError("Укажи 1 или 2 дня выплат, например: 5, 20"); + return; + } + if (payDays.length > 2) { + setError("Можно выбрать максимум 2 дня выплаты"); + return; + } + if (payDays.length === 2 && (!Number.isFinite(percent) || percent < 0 || percent > 100)) { + setError("Процент первой выплаты — от 0 до 100"); + return; + } + + const payload = { + name: name.trim(), + salary_amount: amount, + pay_days: payDays, + first_pay_percent: payDays.length === 1 ? 100 : percent, + weekend_policy: weekendPolicy, + is_active: isActive, + }; + + try { + setSaving(true); + setError(null); + if (editingId == null) { + await createMyJob(payload); + setNotice("Работа добавлена"); + } else { + await updateMyJob(editingId, payload); + setNotice("Работа сохранена"); + } + resetForm(); + await reload(); + } catch (err) { + setError(err instanceof Error ? err.message : "Не удалось сохранить"); + } finally { + setSaving(false); + } + } + + async function onDelete(job: Job) { + const ok = window.confirm(`Удалить работу «${job.name}»?`); + if (!ok) return; + try { + setSaving(true); + setError(null); + await deleteMyJob(job.id); + if (editingId === job.id) resetForm(); + setNotice("Работа удалена"); + await reload(); + } catch (err) { + setError(err instanceof Error ? err.message : "Не удалось удалить"); + } finally { + setSaving(false); + } + } + + return ( + <> + + + + +
+ Новый + + ) : undefined + } + > + {!loading && jobs.length === 0 ? ( + Пока нет работ — добавь первой формой ниже. + ) : ( +
    + {jobs.map((job) => { + const days = [...job.pay_days].sort((a, b) => a - b); + const p1 = job.first_pay_percent; + const p2 = Math.max(0, 100 - p1); + const split = + days.length === 1 + ? `${days[0]} · 100%` + : `${days[0]} · ${p1}% / ${days[1]} · ${p2}%`; + const next = job.next_pays?.[0]; + return ( +
  • +
    + + {job.name} + {!job.is_active ? " · выкл" : ""} + + + {formatMoney(job.salary_amount, job.currency)} · {split} ·{" "} + {weekendLabel(job.weekend_policy)} + {next + ? ` · ближайшая ${formatDate(next.date)} (${formatMoney(next.amount)})` + : ""} + +
    +
    + + +
    +
  • + ); + })} +
+ )} +
+ +
+
void onSubmit(e)}> + setName(e.target.value)} + placeholder="Основная работа" + required + disabled={saving} + /> + setSalary(e.target.value)} + placeholder="80000" + required + disabled={saving} + /> + { + const typed = e.target.value; + const parsed = parsePayDays(typed); + const tokens = typed.split(/[,;\s]+/).filter(Boolean); + if (tokens.length > 2) { + setPayDaysRaw(formatPayDays(parsed)); + setError("Можно выбрать максимум 2 дня выплаты"); + } else { + setPayDaysRaw(typed); + setError(null); + } + }} + placeholder="5, 20" + required + disabled={saving} + hint={ +

+ Максимум два числа месяца. Если дня нет в месяце — последний день месяца. +

+ } + /> + +
+ {QUICK_DAYS.map((day) => { + const on = selectedDays.includes(day); + const blocked = !on && selectedDays.length >= 2; + return ( + + ); + })} +
+ + {selectedDays.length === 2 ? ( +
+ setFirstPercent(e.target.value)} + disabled={saving} + hint={ +

+ На {selectedDays[0]} — {Number.isFinite(Number(firstPercent)) ? firstPercent : "?"}%, + на {selectedDays[1]} —{" "} + {Number.isFinite(secondPercent) ? secondPercent.toFixed(0) : "?"}% +

+ } + /> +
+ ) : null} + +
+ Если день выпал на сб/вс + + +

+ Пример: 20 число — суббота → выплата 19 (до) или 22 (после). +

+
+ + + +
+ + {editingId != null ? ( + + ) : null} +
+ +
+ + ); +} diff --git a/web/src/styles.css b/web/src/styles.css new file mode 100644 index 0000000..0939f48 --- /dev/null +++ b/web/src/styles.css @@ -0,0 +1,2 @@ +/* Design system entry — tokens, base, product components. */ +@import "./design/index.css"; diff --git a/web/src/types.ts b/web/src/types.ts new file mode 100644 index 0000000..c2a3f09 --- /dev/null +++ b/web/src/types.ts @@ -0,0 +1,109 @@ +export type Budget = { + id: number; + user_id: number; + name: string; + total_amount: number; + start_date: string; + end_date: string; + currency: string; + is_active: boolean; +}; + +export type BudgetStatus = { + budget: Budget; + today: string; + days_left: number; + total_spent: number; + remaining: number; + daily_limit: number; + spent_today: number; + remaining_today: number; + is_over_daily: boolean; + is_over_budget: boolean; + is_expired: boolean; + selected: boolean; +}; + +export type Expense = { + id: number; + budget_id: number; + amount: number; + note: string | null; + spent_at: string; +}; + +export type ExpensesPage = { + page: number; + total_pages: number; + total_count: number; + page_size: number; + total_sum: number; + budget_id: number | null; + items: Expense[]; +}; + +export type ExpensesRange = { + items: Expense[]; +}; + +export type BudgetsList = { + items: BudgetStatus[]; +}; + +export type Job = { + id: number; + user_id: number; + name: string; + salary_amount: number; + currency: string; + pay_days: number[]; + first_pay_percent: number; + weekend_policy: "before_weekend" | "after_weekend"; + is_active: boolean; + next_pays: UpcomingPay[]; +}; + +export type UpcomingPay = { + date: string; + scheduled_day: number; + percent: number; + amount: number; +}; + +export type JobsList = { + items: Job[]; +}; + +export type AuthUser = { + user_id: number; + first_name?: string | null; + last_name?: string | null; + username?: string | null; + photo_url?: string | null; +}; + +export type AuthSession = { + access_token: string; + token_type: string; + user: AuthUser; +}; + +export type TelegramLoginPayload = { + id: number; + first_name: string; + last_name?: string; + username?: string; + photo_url?: string; + auth_date: number; + hash: string; +}; + +export type YandexProvider = { + enabled: boolean; + client_id?: string | null; + redirect_uri?: string | null; +}; + +export type AuthProviders = { + yandex: YandexProvider; +}; diff --git a/web/src/ui/AppButton.tsx b/web/src/ui/AppButton.tsx new file mode 100644 index 0000000..aef1b77 --- /dev/null +++ b/web/src/ui/AppButton.tsx @@ -0,0 +1,42 @@ +import type { ButtonHTMLAttributes, ReactNode } from "react"; + +export type AppButtonStyle = "filled" | "tinted" | "gray" | "plain" | "destructive"; +export type AppButtonSize = "large" | "medium" | "small"; + +type Props = ButtonHTMLAttributes & { + label?: ReactNode; + children?: ReactNode; + buttonStyle?: AppButtonStyle; + size?: AppButtonSize; + expanded?: boolean; + loading?: boolean; +}; + +export function AppButton({ + label, + children, + buttonStyle = "filled", + size = "large", + expanded = true, + loading = false, + className = "", + disabled, + type = "button", + ...rest +}: Props) { + const classes = [ + "app-btn", + `app-btn--${buttonStyle}`, + `app-btn--${size}`, + expanded ? "app-btn--expanded" : "", + className, + ] + .filter(Boolean) + .join(" "); + + return ( + + ); +} diff --git a/web/src/ui/AppFeedback.tsx b/web/src/ui/AppFeedback.tsx new file mode 100644 index 0000000..95eec41 --- /dev/null +++ b/web/src/ui/AppFeedback.tsx @@ -0,0 +1,96 @@ +import type { ReactNode } from "react"; +import { AppButton } from "./AppButton"; +import { AppText } from "./AppText"; + +export function AppSpinner() { + return ; +} + +export function AppSkeleton({ width = "100%", height = 12 }: { width?: string | number; height?: number }) { + return ; +} + +export function AppProgress({ + spent, + total, + label, +}: { + spent: number; + total: number; + label?: string; +}) { + const pct = total > 0 ? Math.min(100, Math.max(0, (spent / total) * 100)) : 0; + const tone = pct >= 100 ? "is-danger" : pct >= 85 ? "is-warn" : ""; + return ( +
+
+ +
+
+ ); +} + +export function AppEmptyView({ + title = "Пусто", + message, + action, +}: { + title?: string; + message?: ReactNode; + action?: ReactNode; +}) { + return ( +
+ {title} + {message ? {message} : null} + {action} +
+ ); +} + +export function AppErrorView({ + message, + onRetry, +}: { + message: string; + onRetry?: () => void; +}) { + return ( +
+ Не удалось загрузить + {message} + {onRetry ? ( + + ) : null} +
+ ); +} + +export function AppChip({ + label, + selected = false, + onClick, + disabled, +}: { + label: string; + selected?: boolean; + onClick?: () => void; + disabled?: boolean; +}) { + return ( + + ); +} + +export function AppAvatar({ initials }: { initials: string }) { + return {initials}; +} diff --git a/web/src/ui/AppList.tsx b/web/src/ui/AppList.tsx new file mode 100644 index 0000000..9f87438 --- /dev/null +++ b/web/src/ui/AppList.tsx @@ -0,0 +1,75 @@ +import type { ReactNode } from "react"; +import { AppSectionHeader } from "./AppText"; + +type SectionProps = { + header?: string; + footer?: ReactNode; + children: ReactNode; + flush?: boolean; +}; + +export function AppListSection({ header, footer, children, flush = false }: SectionProps) { + return ( +
+ {header ? {header} : null} +
{children}
+ {footer ?

{footer}

: null} +
+ ); +} + +type TileProps = { + title: ReactNode; + subtitle?: ReactNode; + value?: ReactNode; + leading?: ReactNode; + trailing?: ReactNode; + chevron?: boolean; + destructive?: boolean; + onClick?: () => void; + href?: string; +}; + +export function AppListTile({ + title, + subtitle, + value, + leading, + trailing, + chevron = false, + destructive = false, + onClick, + href, +}: TileProps) { + const interactive = Boolean(onClick || href); + const className = `app-tile${destructive ? " app-tile--destructive" : ""}`; + const body = ( + <> + {leading} +
+

{title}

+ {subtitle ?

{subtitle}

: null} +
+ {trailing ?? (value != null ?

{value}

: null)} + {chevron ? : null} + + ); + + if (href) { + return ( + + {body} + + ); + } + + if (interactive) { + return ( + + ); + } + + return
{body}
; +} diff --git a/web/src/ui/AppSegmented.tsx b/web/src/ui/AppSegmented.tsx new file mode 100644 index 0000000..cc59b86 --- /dev/null +++ b/web/src/ui/AppSegmented.tsx @@ -0,0 +1,32 @@ +type Option = { value: T; label: string }; + +type Props = { + value: T; + options: Option[]; + onChange: (value: T) => void; + ariaLabel?: string; +}; + +export function AppSegmented({ + value, + options, + onChange, + ariaLabel, +}: Props) { + return ( +
+ {options.map((option) => ( + + ))} +
+ ); +} diff --git a/web/src/ui/AppText.tsx b/web/src/ui/AppText.tsx new file mode 100644 index 0000000..547062e --- /dev/null +++ b/web/src/ui/AppText.tsx @@ -0,0 +1,59 @@ +import type { ElementType, ReactNode } from "react"; + +export type AppTextVariant = + | "largeTitle" + | "title1" + | "title2" + | "title3" + | "headline" + | "body" + | "callout" + | "subhead" + | "footnote" + | "caption"; + +export type AppTextTone = "label" | "secondary" | "tertiary" | "accent" | "danger"; + +const DEFAULT_TONE: Record = { + largeTitle: "label", + title1: "label", + title2: "label", + title3: "label", + headline: "label", + body: "label", + callout: "secondary", + subhead: "secondary", + footnote: "secondary", + caption: "tertiary", +}; + +type Props = { + children: ReactNode; + variant?: AppTextVariant; + tone?: AppTextTone; + as?: ElementType; + className?: string; +}; + +export function AppText({ + children, + variant = "body", + tone, + as: Tag = "p", + className = "", +}: Props) { + const color = tone ?? DEFAULT_TONE[variant]; + return ( + + {children} + + ); +} + +export function AppSectionHeader({ children }: { children: ReactNode }) { + return ( + + {typeof children === "string" ? children.toUpperCase() : children} + + ); +} diff --git a/web/src/ui/AppTextField.tsx b/web/src/ui/AppTextField.tsx new file mode 100644 index 0000000..c1e0126 --- /dev/null +++ b/web/src/ui/AppTextField.tsx @@ -0,0 +1,54 @@ +import type { InputHTMLAttributes, ReactNode, SelectHTMLAttributes } from "react"; +import { AppSectionHeader } from "./AppText"; + +type FieldProps = { + label?: string; + hint?: ReactNode; + error?: string; + className?: string; +}; + +export function AppTextField({ + label, + id, + hint, + error, + className = "", + ...rest +}: InputHTMLAttributes & FieldProps & { id: string }) { + return ( +
+ {label ? ( + + ) : null} + + {error ?

{error}

: hint} +
+ ); +} + +export function AppSelect({ + label, + id, + hint, + error, + className = "", + children, + ...rest +}: SelectHTMLAttributes & FieldProps & { id: string }) { + return ( +
+ {label ? ( + + ) : null} + + {error ?

{error}

: hint} +
+ ); +} diff --git a/web/src/ui/index.ts b/web/src/ui/index.ts new file mode 100644 index 0000000..51807e0 --- /dev/null +++ b/web/src/ui/index.ts @@ -0,0 +1,15 @@ +export { AppText, AppSectionHeader } from "./AppText"; +export { AppButton } from "./AppButton"; +export type { AppButtonSize, AppButtonStyle } from "./AppButton"; +export { AppTextField, AppSelect } from "./AppTextField"; +export { AppListSection, AppListTile } from "./AppList"; +export { AppSegmented } from "./AppSegmented"; +export { + AppSpinner, + AppSkeleton, + AppProgress, + AppEmptyView, + AppErrorView, + AppChip, + AppAvatar, +} from "./AppFeedback"; diff --git a/web/src/ui/kit.css b/web/src/ui/kit.css new file mode 100644 index 0000000..dd627b8 --- /dev/null +++ b/web/src/ui/kit.css @@ -0,0 +1,346 @@ +/* iOS kit — mirrors mobile/lib/ui */ + +.app-text { + margin: 0; + font-family: var(--font-sans); +} + +.app-text--largeTitle { + font-size: var(--text-large-title); + font-weight: 700; + letter-spacing: 0.37px; + line-height: 1.2; +} +.app-text--title1 { + font-size: var(--text-title1); + font-weight: 700; + letter-spacing: 0.36px; + line-height: 1.2; +} +.app-text--title2 { + font-size: var(--text-title2); + font-weight: 700; + letter-spacing: 0.35px; + line-height: 1.25; +} +.app-text--title3 { + font-size: var(--text-title3); + font-weight: 600; + letter-spacing: 0.38px; + line-height: 1.25; +} +.app-text--headline { + font-size: var(--text-headline); + font-weight: 600; + letter-spacing: -0.41px; + line-height: 1.3; +} +.app-text--body { + font-size: var(--text-body); + font-weight: 400; + letter-spacing: -0.41px; + line-height: 1.3; +} +.app-text--callout { + font-size: var(--text-callout); + font-weight: 400; + letter-spacing: -0.32px; + line-height: 1.3; +} +.app-text--subhead { + font-size: var(--text-subhead); + font-weight: 400; + letter-spacing: -0.24px; + line-height: 1.3; +} +.app-text--footnote { + font-size: var(--text-footnote); + font-weight: 400; + letter-spacing: -0.08px; + line-height: 1.3; +} +.app-text--caption { + font-size: var(--text-caption1); + font-weight: 400; + line-height: 1.3; +} + +.app-text--label { color: var(--label); } +.app-text--secondary { color: var(--secondary-label); } +.app-text--tertiary { color: var(--tertiary-label); } +.app-text--accent { color: var(--accent); } +.app-text--danger { color: var(--system-red); } + +.app-section-header { + display: block; + margin: 0; + padding: var(--space-4) var(--gutter) var(--space-2); + font-size: var(--text-footnote); + font-weight: 400; + letter-spacing: 0.4px; + text-transform: uppercase; + color: var(--secondary-label); +} + +.app-section-footer { + margin: 0; + padding: var(--space-2) var(--gutter) 0; + font-size: var(--text-footnote); + color: var(--secondary-label); +} + +.app-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + border: 0; + cursor: pointer; + font-family: var(--font-sans); + font-weight: 600; + letter-spacing: -0.41px; + text-decoration: none; + color: inherit; + transition: filter 140ms ease, opacity 140ms ease; +} + +.app-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.app-btn--expanded { width: 100%; } +.app-btn--large { + height: var(--control-height); + padding: 0 var(--space-4); + border-radius: var(--radius-lg); + font-size: var(--text-headline); +} +.app-btn--medium { + height: var(--control-height-md); + padding: 0 var(--space-4); + border-radius: var(--radius-lg); + font-size: var(--text-body); +} +.app-btn--small { + height: var(--control-height-sm); + padding: 0 var(--space-3); + border-radius: var(--radius-md); + font-size: var(--text-subhead); +} + +.app-btn--filled { background: var(--accent); color: #fff; } +.app-btn--tinted { background: rgb(18 136 90 / 15%); color: var(--accent); } +.app-btn--gray { background: var(--system-gray5); color: var(--label); } +.app-btn--plain { background: transparent; color: var(--accent); } +.app-btn--destructive { background: rgb(255 59 48 / 15%); color: var(--system-red); } + +.app-btn:hover:not(:disabled) { filter: brightness(1.04); } + +.app-field { display: grid; gap: var(--space-2); } +.app-field .app-section-header { + padding: 0; +} +.app-field__control { + width: 100%; + height: var(--control-height-md); + border: 0; + border-radius: var(--radius-md); + background: var(--grouped-surface); + color: var(--label); + padding: 0 var(--space-3); + font-size: var(--text-body); + letter-spacing: -0.41px; +} +.app-field__control:disabled { + opacity: 0.4; +} +.app-field--error .app-field__control { + box-shadow: inset 0 0 0 1px var(--system-red); +} +.app-field__error { + margin: 0; + font-size: var(--text-footnote); + color: var(--system-red); +} + +.app-list { + margin: 0 var(--gutter); + padding: 0; + list-style: none; + background: var(--grouped-surface); + border-radius: var(--radius-lg); + overflow: hidden; +} + +.app-list--flush { + margin-inline: 0; +} + +.app-tile { + display: flex; + align-items: center; + gap: var(--space-3); + width: 100%; + min-height: var(--row-min-height); + padding: 10px var(--gutter); + border: 0; + background: var(--grouped-surface); + color: inherit; + text-align: left; + font: inherit; + cursor: default; +} +button.app-tile, +a.app-tile { + cursor: pointer; +} +button.app-tile:hover, +a.app-tile:hover { + background: var(--system-gray5); +} +.app-tile + .app-tile { + box-shadow: inset 0 var(--hairline) 0 var(--separator); + background-clip: padding-box; +} +.app-tile__body { flex: 1; min-width: 0; } +.app-tile__title { + margin: 0; + font-size: var(--text-body); + letter-spacing: -0.41px; + color: var(--label); +} +.app-tile--destructive .app-tile__title { color: var(--system-red); } +.app-tile__subtitle { + margin: 2px 0 0; + font-size: var(--text-footnote); + color: var(--secondary-label); +} +.app-tile__value { + margin: 0; + font-size: var(--text-body); + color: var(--secondary-label); + font-variant-numeric: tabular-nums; +} +.app-tile__chevron { + color: var(--tertiary-label); + font-size: 17px; + line-height: 1; +} + +.app-chip { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 28px; + padding: 0 12px; + border: 0; + border-radius: var(--radius-capsule); + background: var(--system-gray5); + color: var(--label); + font-size: var(--text-subhead); + cursor: pointer; +} +.app-chip.is-on { + background: rgb(18 136 90 / 15%); + color: var(--accent); +} + +.app-avatar { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: var(--radius-capsule); + background: var(--accent-soft); + color: var(--accent); + font-size: var(--text-subhead); + font-weight: 600; +} + +.app-progress__track { + height: 4px; + border-radius: var(--radius-capsule); + background: var(--system-gray5); + overflow: hidden; +} +.app-progress__bar { + display: block; + height: 100%; + background: var(--accent); + border-radius: inherit; +} +.app-progress__bar.is-warn { background: var(--system-orange); } +.app-progress__bar.is-danger { background: var(--system-red); } + +.app-spinner { + width: 20px; + height: 20px; + border: 2px solid var(--system-gray3); + border-top-color: var(--secondary-label); + border-radius: 50%; + animation: app-spin 700ms linear infinite; +} +@keyframes app-spin { to { transform: rotate(360deg); } } + +.app-skeleton { + display: block; + height: 12px; + border-radius: var(--radius-sm); + background: var(--system-gray5); +} + +.app-empty, +.app-error { + display: grid; + justify-items: center; + gap: var(--space-3); + padding: var(--space-7) var(--gutter); + text-align: center; + color: var(--secondary-label); +} + +.app-segmented { + display: flex; + padding: 2px; + border-radius: 9px; + background: var(--system-gray5); +} +.app-segmented__item { + flex: 1; + min-height: 32px; + border: 0; + border-radius: 7px; + background: transparent; + color: var(--label); + font-size: var(--text-subhead); + font-weight: 500; + cursor: pointer; +} +.app-segmented__item.is-on { + background: var(--grouped-surface); + box-shadow: 0 1px 3px rgb(0 0 0 / 12%); +} + +.app-toast { + position: fixed; + left: 50%; + bottom: 24px; + transform: translateX(-50%); + z-index: var(--z-toast); + display: flex; + align-items: center; + gap: var(--space-2); + min-height: 44px; + padding: 0 16px; + border-radius: var(--radius-capsule); + background: rgb(28 28 30 / 88%); + color: #fff; + font-size: var(--text-subhead); + backdrop-filter: blur(16px); +} + +@media (prefers-color-scheme: dark) { + .app-btn--tinted { background: rgb(60 214 140 / 18%); } +} diff --git a/web/src/vite-env.d.ts b/web/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/web/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json new file mode 100644 index 0000000..97c1c97 --- /dev/null +++ b/web/tsconfig.app.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..d32ff68 --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] +} diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json new file mode 100644 index 0000000..a31c1bd --- /dev/null +++ b/web/tsconfig.node.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 0000000..77a2411 --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig, loadEnv } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ""); + // Dev: /api → localhost:51291 (как nginx в Docker) + const apiProxyTarget = env.VITE_API_PROXY_TARGET || "http://localhost:51291"; + + return { + plugins: [react()], + server: { + port: 51290, + proxy: { + "/api": { + target: apiProxyTarget, + changeOrigin: true, + }, + }, + }, + }; +});