Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fb11a70f2 | ||
|
|
acd669a942 | ||
|
|
e99d21c6d3 | ||
|
|
b80fe40852 | ||
|
|
4a7ea33185 | ||
|
|
7ddc7157b5 | ||
|
|
c2fd16e2d8 | ||
|
|
64234f8639 | ||
|
|
e99aafa36a | ||
|
|
af151f254b | ||
|
|
1fbec06c23 | ||
|
|
bcfd4acfad | ||
|
|
6a6a95e863 | ||
|
|
54c670ddec | ||
|
|
bcdf2b5154 | ||
|
|
489014667c | ||
|
|
3e0e82f016 | ||
|
|
f5035b0aa2 | ||
|
|
608e7ce0f0 | ||
|
|
8d31078e3d | ||
|
|
962f250a8d | ||
|
|
7505f26c98 | ||
|
|
56e6476484 |
+6
-1
@@ -15,4 +15,9 @@ __pycache__/
|
|||||||
|
|
||||||
# OS мусор
|
# OS мусор
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# frontend
|
||||||
|
frontend/node_modules
|
||||||
|
frontend/dist
|
||||||
|
frontend/configs/.env
|
||||||
+2
-1
@@ -34,4 +34,5 @@ logs/
|
|||||||
input/
|
input/
|
||||||
output/
|
output/
|
||||||
allure-results/
|
allure-results/
|
||||||
.coverage
|
.coverage
|
||||||
|
graphify-out/
|
||||||
@@ -20,6 +20,13 @@ workers. Python >=3.13,<4.0, Poetry for dependency management.
|
|||||||
- `secure` flag on refresh_token cookie is driven by `env_settings.PROD_MODE`
|
- `secure` flag on refresh_token cookie is driven by `env_settings.PROD_MODE`
|
||||||
(bool) — `False` locally/tests so cookies work over plain HTTP, `True` in
|
(bool) — `False` locally/tests so cookies work over plain HTTP, `True` in
|
||||||
prod.
|
prod.
|
||||||
|
- **CORS**: `CORSMiddleware` in `main.py` with `allow_origins=[env_settings.
|
||||||
|
FRONT_URL]` and `allow_credentials=True` (needed for the httpOnly
|
||||||
|
`refresh_token` cookie — with credentials, `["*"]` is rejected by browsers).
|
||||||
|
`FRONT_URL` is a required `Env` field with no default, so the backend fails
|
||||||
|
to start without it in `configs/.env` (e.g. `http://localhost:5173`). It is
|
||||||
|
added last, after the logging middlewares, so it wraps them and CORS
|
||||||
|
headers also appear on error responses (401/429).
|
||||||
|
|
||||||
## Redis (`src/cache/`)
|
## Redis (`src/cache/`)
|
||||||
|
|
||||||
@@ -27,12 +34,15 @@ workers. Python >=3.13,<4.0, Poetry for dependency management.
|
|||||||
`redis.Redis` directly (inherits all commands, no manual wrapping needed).
|
`redis.Redis` directly (inherits all commands, no manual wrapping needed).
|
||||||
- Three uses: permissions is-cache was considered and rejected (no real DB
|
- Three uses: permissions is-cache was considered and rejected (no real DB
|
||||||
savings — `get_user_by_id` already eager-loads everything via `selectin`
|
savings — `get_user_by_id` already eager-loads everything via `selectin`
|
||||||
in one call); rate limiting on login (`RateLimit.rate_limit(ip)` —
|
in one call); rate limiting (`RateLimit.rate_limit(ip, action, limit=5,
|
||||||
`INCR` + `EXPIRE` on first attempt, blocks >5/60s); access-token revoke
|
window=60)` — `INCR` + `EXPIRE ... NX` on first attempt, key
|
||||||
blacklist (see above).
|
`rate_limit:{action}:{ip}`, so each action has its own counter); access-token
|
||||||
- Rate limit is only triggered inside `except HTTPException` on `/protected/token`
|
revoke blacklist (see above).
|
||||||
— i.e. only on failed logins, not successful ones (otherwise legitimate
|
- Two rate-limited actions: `"login"` (5/60s) and `"reset"` (3/600s).
|
||||||
repeated logins would trip it).
|
The login limit is only triggered inside `except HTTPException` on
|
||||||
|
`/protected/token` — i.e. only on failed logins, not successful ones
|
||||||
|
(otherwise legitimate repeated logins would trip it). `/protected/reset`
|
||||||
|
counts every call.
|
||||||
|
|
||||||
## RabbitMQ (`src/messaging/`)
|
## RabbitMQ (`src/messaging/`)
|
||||||
|
|
||||||
@@ -89,11 +99,26 @@ workers. Python >=3.13,<4.0, Poetry for dependency management.
|
|||||||
`src/service/email/templates/`), inline CSS (email clients don't support
|
`src/service/email/templates/`), inline CSS (email clients don't support
|
||||||
`<style>` reliably), `EmailMessage` with `set_content()` (plain-text
|
`<style>` reliably), `EmailMessage` with `set_content()` (plain-text
|
||||||
fallback) + `add_alternative(html, subtype="html")`.
|
fallback) + `add_alternative(html, subtype="html")`.
|
||||||
- **Known gap**: the reset-password flow is a stub. `ResetEmailConsumer.process_message`
|
- Reset-password flow mirrors welcome: `ResetEmailSender`
|
||||||
only prints and acks — it never calls a sender — and
|
(`src/service/email/email_reset.py`) renders `templates/reset.html`
|
||||||
`src/service/email/email_reset.py` is empty. `templates/reset.html` still
|
(`{{ temp_password }}`, no longer hardcoded) the same way
|
||||||
has a hardcoded placeholder password. Don't assume reset emails actually
|
`DaemonEmailSender` does `welcome.html`. `ResetEmailConsumer.process_message`
|
||||||
send until this is wired up like `WelcomeEmailConsumer`/`DaemonEmailSender`.
|
reads both `email` and `temp_password` from the message body and uses the
|
||||||
|
same transient/permanent classification as `WelcomeEmailConsumer`.
|
||||||
|
`EmailProducer.send_reset_email(email, temp_password)` takes the password
|
||||||
|
as a second argument.
|
||||||
|
- **Reset route**: `POST /protected/reset?email=...` in `auth_routes.py` —
|
||||||
|
`email` is a plain `str` param, so FastAPI reads it from the **query
|
||||||
|
string**, not the JSON body. The route rate-limits (3/600s per IP), calls
|
||||||
|
`CrudService.reset_password(email)` (generates a 16-char temp password via
|
||||||
|
`HashService.generate_temp_password()` — which is `async` — hashes and
|
||||||
|
saves it through `update_user`), then publishes `{email, temp_password}` to
|
||||||
|
`email.reset`. A 404 (unknown email) is swallowed so the response is always
|
||||||
|
`{"detail": "New password was sent"}` — no user enumeration. Known rough
|
||||||
|
edge: the DB password is changed *before* the email is sent, so a message
|
||||||
|
that ends up in the DLQ leaves the user with a changed password and no
|
||||||
|
email; and anyone who knows an email can reset that account's password.
|
||||||
|
A token-link flow would avoid both.
|
||||||
|
|
||||||
## Logging (`src/logging/`)
|
## Logging (`src/logging/`)
|
||||||
|
|
||||||
@@ -172,6 +197,9 @@ workers. Python >=3.13,<4.0, Poetry for dependency management.
|
|||||||
see the `src/logging/logger.py` vs `http_logger.py` split above for why
|
see the `src/logging/logger.py` vs `http_logger.py` split above for why
|
||||||
that boundary is enforced deliberately.
|
that boundary is enforced deliberately.
|
||||||
- `dev` group: pytest stack, allure, httpie, requests-async.
|
- `dev` group: pytest stack, allure, httpie, requests-async.
|
||||||
|
- `claude` group: `graphifyy` (the `/graphify` Claude Code skill) — dev
|
||||||
|
tooling only, never referenced by any Dockerfile stage, install with
|
||||||
|
`poetry install --with claude` when needed locally.
|
||||||
- Dockerfile has parallel builder→final stage pairs: `builder`→`prod`
|
- Dockerfile has parallel builder→final stage pairs: `builder`→`prod`
|
||||||
(installs `main,web`) and `worker-builder`→`worker` (installs
|
(installs `main,web`) and `worker-builder`→`worker` (installs
|
||||||
`main,daemon`). Same base pattern: venv builder stage copies
|
`main,daemon`). Same base pattern: venv builder stage copies
|
||||||
@@ -187,6 +215,75 @@ workers. Python >=3.13,<4.0, Poetry for dependency management.
|
|||||||
override `DB_HOST` via `environment:`; the `.env` file's own default is
|
override `DB_HOST` via `environment:`; the `.env` file's own default is
|
||||||
for host-side runs.
|
for host-side runs.
|
||||||
|
|
||||||
|
## Codebase knowledge graph (graphify)
|
||||||
|
|
||||||
|
- `/graphify` (Claude Code skill, `graphifyy` package in the `claude`
|
||||||
|
Poetry group) builds a navigable knowledge graph of this repo into
|
||||||
|
`graphify-out/` (`graph.html`, `GRAPH_REPORT.md`, `graph.json`) —
|
||||||
|
gitignored, regenerable, never commit it.
|
||||||
|
- Its own interpreter/venv detection defaults to `uv tool`/pipx-style
|
||||||
|
installs; **this project has neither** — `graphifyy` is installed
|
||||||
|
straight into the project's own `.venv` via Poetry, so point graphify
|
||||||
|
at `.venv/bin/python3` directly rather than letting it search for `uv`.
|
||||||
|
- The sandbox's blanket `**/.env`/`**/.env.*` read-deny blocks graphify
|
||||||
|
from reading `configs/.env.example` too (not just the real `.env`) —
|
||||||
|
it shows up as `skipped_sensitive` in detection. This is a sandbox-level
|
||||||
|
block, not graphify's own sensitive-file heuristic; a real `.env` would
|
||||||
|
be excluded by graphify anyway, but `.env.example` (no real secrets)
|
||||||
|
would otherwise be safe to include if the sandbox allowed reading it.
|
||||||
|
- A `GRAPH HEALTH WARNING` (dangling-endpoint edges, collapsed edges) on
|
||||||
|
a fresh build most likely means the AST extractor's node-ID format
|
||||||
|
changed since a previous partial run — re-run with `graphify extract
|
||||||
|
--force` if it persists across rebuilds; a one-off warning on first
|
||||||
|
build isn't necessarily a problem.
|
||||||
|
|
||||||
|
## Frontend (`frontend/`)
|
||||||
|
|
||||||
|
- React 19 + TypeScript + Vite + `react-router-dom` 7, package manager is
|
||||||
|
**yarn** (`yarn.lock`). Run with `yarn dev` (Vite, default
|
||||||
|
`http://localhost:5173`). The user writes/edits frontend code themselves —
|
||||||
|
explain rather than edit unless asked (see memory).
|
||||||
|
- Layout: `src/features/auth/components/` (`AuthComponent`, `ResetComponent`,
|
||||||
|
each with its own `.module.css`), `src/pages/` (`LoginPage` at `/`, `ForgotPasswordPage`
|
||||||
|
at `/forgot-password` — thin wrappers), `src/layouts/AuthLayout` (shared
|
||||||
|
split-screen shell with the marketing block; takes `children` for the form
|
||||||
|
on the right), `src/api/` (`token.ts`, `LoginAPI/Auth.ts`,
|
||||||
|
`ResetAPI/Reset.ts`), `src/types/UserTypes.ts` (mirrors the Pydantic
|
||||||
|
`UserOut`/`UserCreate`). `index.css` = global reset/font, `App.css` = `#root`
|
||||||
|
frame.
|
||||||
|
- **Env**: Vite reads `.env*` from `frontend/configs/` (`envDir: "./configs"`
|
||||||
|
in `vite.config.ts`), not from `frontend/`. Only `VITE_`-prefixed variables
|
||||||
|
reach the browser code, via `import.meta.env`. Backend address is
|
||||||
|
`VITE_BACKEND_URL`, exported as `API_URL` from `src/api/token.ts`. Restart
|
||||||
|
the dev server after editing `.env`. There is **no** Vite `server.proxy`
|
||||||
|
(removed on purpose) — the browser calls the backend directly, so backend
|
||||||
|
CORS (see Auth) must allow the frontend origin.
|
||||||
|
- **Access token** lives in a module variable in `src/api/token.ts`
|
||||||
|
(`setAccessToken`/`getAccessToken`), not in `localStorage`; it is lost on
|
||||||
|
page reload (a `/protected/refresh` call on startup is not implemented
|
||||||
|
yet). `authFetch(path, init)` adds `Authorization: Bearer` and
|
||||||
|
`credentials: "include"`. Login (`POST /protected/token`) must be sent as
|
||||||
|
**form-data** (`URLSearchParams` with `username` = email and `password`),
|
||||||
|
because the route uses `OAuth2PasswordRequestForm`, and returns
|
||||||
|
`{access_token, token_type}`; the refresh token arrives as an httpOnly cookie.
|
||||||
|
- Forms are controlled inputs (`useState`) with hand-written validation:
|
||||||
|
per-field `errors` state, checked on blur and on submit; `<form noValidate>`
|
||||||
|
so browser bubbles don't preempt it. Invalid field gets `.inputError`, text
|
||||||
|
goes in an absolutely positioned `.errorText` under the field. Backend
|
||||||
|
errors are logged with `console.error` and shown as a short generic message.
|
||||||
|
- **CSS Modules gotchas**: only *class* selectors are hashed. Bare element
|
||||||
|
selectors (`form {}`, `input {}`) and `#id` selectors stay global and leak
|
||||||
|
between components (one module's `form { gap }` restyled the other form).
|
||||||
|
Scope them under the module's own class (`.wrapper_1 form {}`) — the
|
||||||
|
hashed ancestor makes the rule local. Classes with hyphens are accessed as
|
||||||
|
`styles["email-icon"]`. Class names shared between two modules are
|
||||||
|
independent copies (duplicate the rules, don't rely on sharing).
|
||||||
|
- React 19 types: `FormEvent` is deprecated; use `SubmitEvent<HTMLFormElement>`.
|
||||||
|
`<p>` must not contain `<div>` (hydration warning).
|
||||||
|
- Login currently redirects to `http://localhost:8000/docs` after success
|
||||||
|
(placeholder — no post-login page yet); `ResetComponent` copies that
|
||||||
|
placeholder redirect too.
|
||||||
|
|
||||||
## Testing (`tests/unit`, `tests/integrated`, `tests/e2e`)
|
## Testing (`tests/unit`, `tests/integrated`, `tests/e2e`)
|
||||||
|
|
||||||
- **Recurring root cause of "different event loop" / `MissingGreenlet`-style
|
- **Recurring root cause of "different event loop" / `MissingGreenlet`-style
|
||||||
@@ -204,3 +301,18 @@ workers. Python >=3.13,<4.0, Poetry for dependency management.
|
|||||||
which broke refresh-token-cookie-dependent tests like logout).
|
which broke refresh-token-cookie-dependent tests like logout).
|
||||||
- `test_user_fixture` is `indirect=True` parametrized with
|
- `test_user_fixture` is `indirect=True` parametrized with
|
||||||
`(direct_permissions, group)` tuples.
|
`(direct_permissions, group)` tuples.
|
||||||
|
- `tests/unit/test_consumers.py` covers `RabbitMQClient`/`WelcomeEmailConsumer`/
|
||||||
|
`ResetEmailConsumer` entirely with mocks — no real broker involved.
|
||||||
|
Pattern: `monkeypatch.setattr(rabbitmq_client_module.aio_pika, "connect_robust", ...)`
|
||||||
|
patches the module attribute that `connect()` looks up at call time (not
|
||||||
|
the `rabbitmq_client` singleton's method — that's a bound method, it has
|
||||||
|
no attribute of its own to patch). `aio_pika.connect_robust`/`asyncio.sleep`
|
||||||
|
must both be mocked when testing the retry loop, or the test really
|
||||||
|
sleeps `2**attempt` seconds between attempts. `message.process(...)` is an
|
||||||
|
async context manager, not a plain awaitable — mocking it needs a
|
||||||
|
`MagicMock` with `__aenter__`/`__aexit__` set to `AsyncMock`s (see
|
||||||
|
`make_fake_message()` in that file), not just `AsyncMock()`. When
|
||||||
|
asserting on what a mocked async method returned, compare against
|
||||||
|
`mock.return_value` (or a variable captured before assigning it), never
|
||||||
|
against the mock itself — `some_mock is some_mock.return_value` is never
|
||||||
|
true, they're two different objects.
|
||||||
|
|||||||
@@ -25,5 +25,11 @@ SMTP_SERVER="change_me"
|
|||||||
EMAIL_LOGIN="change_me"
|
EMAIL_LOGIN="change_me"
|
||||||
EMAIL_PASSWORD="change_me"
|
EMAIL_PASSWORD="change_me"
|
||||||
|
|
||||||
|
FRONT_HOST="change_me"
|
||||||
|
FRONT_PORT=change_me #int
|
||||||
|
|
||||||
|
VITE_BACKEND_HOST="change_me"
|
||||||
|
VITE_BACKEND_PORT="change_me"
|
||||||
|
|
||||||
|
|
||||||
PROD_MODE=bool
|
PROD_MODE=bool
|
||||||
+60
-2
@@ -16,6 +16,8 @@ services:
|
|||||||
- DB_HOST=psql #rewrite DB_HOST var to communicate inside the docker network
|
- DB_HOST=psql #rewrite DB_HOST var to communicate inside the docker network
|
||||||
- RABBITMQ_HOST=rabbitmq
|
- RABBITMQ_HOST=rabbitmq
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
|
- FRONT_HOST=${FRONT_HOST}
|
||||||
|
- FRONT_PORT=${FRONT_PORT}
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ../src
|
source: ../src
|
||||||
@@ -48,7 +50,7 @@ services:
|
|||||||
rabbitmq:
|
rabbitmq:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
ports:
|
ports:
|
||||||
- "80:8000"
|
- "${VITE_BACKEND_PORT}:8000"
|
||||||
entrypoint: ["./entrypoint.sh", "--dev"]
|
entrypoint: ["./entrypoint.sh", "--dev"]
|
||||||
|
|
||||||
backend-prod:
|
backend-prod:
|
||||||
@@ -65,6 +67,8 @@ services:
|
|||||||
- DB_HOST=psql
|
- DB_HOST=psql
|
||||||
- RABBITMQ_HOST=rabbitmq
|
- RABBITMQ_HOST=rabbitmq
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
|
- FRONT_HOST=${FRONT_HOST}
|
||||||
|
- FRONT_PORT=${FRONT_PORT}
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
source: ../configs
|
source: ../configs
|
||||||
@@ -89,7 +93,7 @@ services:
|
|||||||
rabbitmq:
|
rabbitmq:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
ports:
|
ports:
|
||||||
- "80:8000"
|
- "${VITE_BACKEND_PORT}:8000"
|
||||||
|
|
||||||
daemons:
|
daemons:
|
||||||
profiles: ["prod", "dev"]
|
profiles: ["prod", "dev"]
|
||||||
@@ -185,6 +189,60 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- backend
|
- backend
|
||||||
|
|
||||||
|
nginx-dev:
|
||||||
|
image: "${DOCKER_REGISTRY:-local}/excel-frontend-dev:${IMAGE_TAG:-local}"
|
||||||
|
container_name: frontend-dev
|
||||||
|
profiles: ["dev"]
|
||||||
|
build:
|
||||||
|
dockerfile: ./docker/dockerfile
|
||||||
|
context: ../
|
||||||
|
target: frontend_dev
|
||||||
|
init: true
|
||||||
|
ipc: private
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: ../frontend/src
|
||||||
|
target: /home/excel-project/src
|
||||||
|
- type: bind
|
||||||
|
source: ../frontend/public
|
||||||
|
target: /home/excel-project/public
|
||||||
|
- type: bind
|
||||||
|
source: ../frontend/index.html
|
||||||
|
target: /home/excel-project/index.html
|
||||||
|
- type: bind
|
||||||
|
source: ../frontend/vite.config.ts
|
||||||
|
target: /home/excel-project/vite.config.ts
|
||||||
|
- type: bind
|
||||||
|
source: ../configs
|
||||||
|
target: /home/configs
|
||||||
|
ports:
|
||||||
|
- "${FRONT_PORT}:5173"
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
restart: always
|
||||||
|
entrypoint: ["yarn", "dev", "--host", "0.0.0.0"]
|
||||||
|
|
||||||
|
nginx-prod:
|
||||||
|
image: "${DOCKER_REGISTRY:-local}/excel-frontend-prod:${IMAGE_TAG:-local}"
|
||||||
|
container_name: frontend-prod
|
||||||
|
profiles: ["prod"]
|
||||||
|
build:
|
||||||
|
dockerfile: ./docker/dockerfile
|
||||||
|
context: ../
|
||||||
|
target: frontend_prod
|
||||||
|
environment:
|
||||||
|
- BACKEND_HOST=backend-prod
|
||||||
|
- BACKEND_PORT=${VITE_BACKEND_PORT}
|
||||||
|
init: true
|
||||||
|
ipc: private
|
||||||
|
ports:
|
||||||
|
- "${FRONT_PORT}:80"
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
backend:
|
backend:
|
||||||
name: "${BACKEND_NETWORK:-backend_network}"
|
name: "${BACKEND_NETWORK:-backend_network}"
|
||||||
@@ -37,6 +37,7 @@ RUN pip install --no-cache-dir poetry \
|
|||||||
&& poetry install --no-root --no-interaction --only main,web \
|
&& poetry install --no-root --no-interaction --only main,web \
|
||||||
&& pip uninstall -y poetry poetry-core poetry-plugin-export
|
&& pip uninstall -y poetry poetry-core poetry-plugin-export
|
||||||
|
|
||||||
|
|
||||||
# --- Stage 2: Python Backend prod ---
|
# --- Stage 2: Python Backend prod ---
|
||||||
|
|
||||||
FROM python:3.14-slim AS prod
|
FROM python:3.14-slim AS prod
|
||||||
@@ -64,6 +65,7 @@ USER appuser
|
|||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh", "--prod"]
|
ENTRYPOINT ["./entrypoint.sh", "--prod"]
|
||||||
|
|
||||||
|
|
||||||
# --- Stage 1: Python daemon builder ---
|
# --- Stage 1: Python daemon builder ---
|
||||||
|
|
||||||
FROM python:3.14-slim AS worker-builder
|
FROM python:3.14-slim AS worker-builder
|
||||||
@@ -85,6 +87,7 @@ RUN pip install --no-cache-dir poetry \
|
|||||||
&& poetry install --no-root --no-interaction --only main,daemon \
|
&& poetry install --no-root --no-interaction --only main,daemon \
|
||||||
&& pip uninstall -y poetry poetry-core poetry-plugin-export
|
&& pip uninstall -y poetry poetry-core poetry-plugin-export
|
||||||
|
|
||||||
|
|
||||||
# --- Stage 2: Python daemons ---
|
# --- Stage 2: Python daemons ---
|
||||||
|
|
||||||
FROM python:3.14-slim AS daemon
|
FROM python:3.14-slim AS daemon
|
||||||
@@ -109,3 +112,54 @@ RUN groupadd --gid 1000 appuser \
|
|||||||
USER appuser
|
USER appuser
|
||||||
|
|
||||||
ENTRYPOINT ["python","-u", "daemon_run.py", "--all"]
|
ENTRYPOINT ["python","-u", "daemon_run.py", "--all"]
|
||||||
|
|
||||||
|
|
||||||
|
# --- Stage 1: Node Frontend builder ---
|
||||||
|
|
||||||
|
FROM node:24-alpine AS frontend-builder
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="The-DisExcel-project-prod"
|
||||||
|
LABEL org.opencontainers.image.source="https://git.homyk.space/MH.Dmitrii/The_DisExcel_project"
|
||||||
|
|
||||||
|
WORKDIR /home/excel-project
|
||||||
|
|
||||||
|
COPY frontend/package.json frontend/yarn.lock ./
|
||||||
|
|
||||||
|
RUN yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
COPY frontend/ ./
|
||||||
|
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
|
||||||
|
# --- Stage 2: Node Frontend prod ---
|
||||||
|
|
||||||
|
FROM nginx:alpine AS frontend_prod
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="The-DisExcel-project-prod"
|
||||||
|
LABEL org.opencontainers.image.source="https://git.homyk.space/MH.Dmitrii/The_DisExcel_project"
|
||||||
|
|
||||||
|
COPY --from=frontend-builder /home/excel-project/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
COPY docker/nginx/nginx.conf /etc/nginx/templates/default.conf.template
|
||||||
|
|
||||||
|
|
||||||
|
# --- Stage 1: Node Frontend dev ---
|
||||||
|
|
||||||
|
FROM node:24-alpine AS frontend_dev
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="The-DisExcel-project-prod"
|
||||||
|
LABEL org.opencontainers.image.source="https://git.homyk.space/MH.Dmitrii/The_DisExcel_project"
|
||||||
|
|
||||||
|
WORKDIR /home/excel-project
|
||||||
|
|
||||||
|
COPY frontend/package.json frontend/yarn.lock ./
|
||||||
|
|
||||||
|
RUN yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /protected/ {
|
||||||
|
proxy_pass http://${BACKEND_HOST}:${BACKEND_PORT};
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
approvedGitRepositories:
|
||||||
|
- "**"
|
||||||
|
|
||||||
|
enableScripts: true
|
||||||
|
|
||||||
|
nodeLinker: node-modules
|
||||||
|
|
||||||
|
npmMinimalAgeGate: 0
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
reactHooks.configs.flat.recommended,
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>frontend</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.2.8",
|
||||||
|
"react-dom": "^19.2.8",
|
||||||
|
"react-router-dom": "^7.18.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@types/node": "^24.13.3",
|
||||||
|
"@types/react": "^19.2.18",
|
||||||
|
"@types/react-dom": "^19.2.7",
|
||||||
|
"@vitejs/plugin-react": "^6.1.1",
|
||||||
|
"eslint": "^10.10.0",
|
||||||
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.5.6",
|
||||||
|
"globals": "^17.12.0",
|
||||||
|
"typescript": "~6.0.2",
|
||||||
|
"typescript-eslint": "^8.69.0",
|
||||||
|
"vite": "^8.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import './App.css'
|
||||||
|
import { Route, Routes } from "react-router-dom";
|
||||||
|
import { LoginPage } from "./pages/LoginPage";
|
||||||
|
import { ForgotPasswordPage } from "./pages/ForgotPasswordPage";
|
||||||
|
import { DashboardPage } from './pages/Dashboard';
|
||||||
|
import { PermissionProvider } from './contexts/PermissionProvider';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<LoginPage />} />
|
||||||
|
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
|
||||||
|
<Route path="/dashboard" element={
|
||||||
|
<PermissionProvider>
|
||||||
|
<DashboardPage />
|
||||||
|
</PermissionProvider>} />
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { API_URL, setAccessToken } from "../token";
|
||||||
|
|
||||||
|
export async function login(email: string, password: string) {
|
||||||
|
const body = new URLSearchParams({ username: email, password });
|
||||||
|
const res = await fetch(`${API_URL}/protected/token`, {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
if (!res.ok){
|
||||||
|
throw new Error("Неверный email или пароль");
|
||||||
|
};
|
||||||
|
const data = await res.json();
|
||||||
|
setAccessToken(data.access_token);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { API_URL, setAccessToken } from "../token";
|
||||||
|
|
||||||
|
export async function refreshToken(): Promise<void> {
|
||||||
|
const response = await fetch(`${API_URL}/protected/refresh`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Session update wasn't successful");
|
||||||
|
};
|
||||||
|
const data = await response.json();
|
||||||
|
setAccessToken(data.access_token);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
export async function requestPasswordReset(email: string): Promise<void> {
|
||||||
|
const res = await fetch(`http://localhost:8000/protected/reset?${new URLSearchParams({ email })}`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = await res.json().catch(function(){
|
||||||
|
return null
|
||||||
|
});
|
||||||
|
throw new Error(typeof body?.detail === "string" ? body.detail : "Не удалось отправить запрос");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { UserOut } from "../../types/UserTypes";
|
||||||
|
import { authFetch } from "../token";
|
||||||
|
|
||||||
|
|
||||||
|
export async function fetchUser(): Promise<UserOut> {
|
||||||
|
const response = await authFetch("/protected/me");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Not authenticated");
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
console.log(data)
|
||||||
|
return data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { refreshToken } from "./RefreshAPI/Refresh";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const API_HOST = import.meta.env.VITE_BACKEND_HOST ?? "";
|
||||||
|
const API_PORT = import.meta.env.VITE_BACKEND_PORT ?? "";
|
||||||
|
|
||||||
|
export const API_URL = API_HOST ? `${API_HOST}:${API_PORT}` : "";
|
||||||
|
|
||||||
|
|
||||||
|
let accessToken: string | null = null;
|
||||||
|
let refreshTimer: ReturnType<typeof setInterval> | null =null;
|
||||||
|
|
||||||
|
export function setAccessToken(token: string | null):void{
|
||||||
|
accessToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAccessToken():string|null {
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function authFetch(path: string, init: RequestInit = {}) {
|
||||||
|
const headers = new Headers(init.headers);
|
||||||
|
if (accessToken) {
|
||||||
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
||||||
|
};
|
||||||
|
return fetch(`${API_URL}${path}`, { ...init, headers, credentials: "include" });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startRefreshTimer(intervalms:number) {
|
||||||
|
stopRefreshTimer();
|
||||||
|
refreshTimer = setInterval(function () {
|
||||||
|
refreshToken().catch(function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
}, intervalms);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopRefreshTimer() {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearInterval(refreshTimer);
|
||||||
|
}
|
||||||
|
refreshTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logout() {
|
||||||
|
try {
|
||||||
|
await authFetch(`/protected/logout`);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error("Something went wrong", { cause: err });
|
||||||
|
}
|
||||||
|
|
||||||
|
setAccessToken(null);
|
||||||
|
stopRefreshTimer();
|
||||||
|
localStorage.removeItem("rememberMe");
|
||||||
|
sessionStorage.removeItem("rememberMe");
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 753 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 749 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 771 KiB |
@@ -0,0 +1,8 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
export type PermissionsState = {
|
||||||
|
permissions: Set<string>;
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PermissionContext = createContext<PermissionsState | null>(null);
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { useEffect, useState, type ReactNode } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { loadPermissions } from "../utils/permissions";
|
||||||
|
import { PermissionContext } from "./PermissionContext";
|
||||||
|
|
||||||
|
export function PermissionProvider({ children }: { children: ReactNode }) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [permissions, setPermissions] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
useEffect(function () {
|
||||||
|
async function load() {
|
||||||
|
try {
|
||||||
|
const response = await loadPermissions();
|
||||||
|
setPermissions(response);
|
||||||
|
setLoading(false);
|
||||||
|
} catch {
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PermissionContext value={{ permissions, loading }}>
|
||||||
|
{children}
|
||||||
|
</PermissionContext>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
.wrapper_1 form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: whitesmoke;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10%;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.Title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
line-height: 45px;
|
||||||
|
letter-spacing: 1.2px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.Description{
|
||||||
|
font-size: medium;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 30px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.Fields {
|
||||||
|
line-height: 40px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.wrapper_1 {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
.wrapper_1 input {
|
||||||
|
line-height: 40px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid rgba(68, 68, 68, 0.3);
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer{
|
||||||
|
display: flex;
|
||||||
|
padding-top: 10%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.submitbtn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 10%;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
.submit {
|
||||||
|
background-color: #087A55;
|
||||||
|
height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: larger;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.submit-arrow {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.email-icon, .pass-icon {
|
||||||
|
height: 25px;
|
||||||
|
position: absolute;
|
||||||
|
left: 15px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
.email_field, .pass_field {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.email_field input, .pass_field input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 15px 12px 45px;
|
||||||
|
transition: padding-left 0.2s ease;
|
||||||
|
}
|
||||||
|
.pass_field input {
|
||||||
|
padding-right: 45px;
|
||||||
|
}
|
||||||
|
.toggle-password {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.toggle-password svg {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
.email_field input:focus,
|
||||||
|
.email_field input:not(:placeholder-shown) {
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
.email_field input:focus + .email-icon,
|
||||||
|
.email_field input:not(:placeholder-shown) + .email-icon {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pass_field input:focus,
|
||||||
|
.pass_field input:not(:placeholder-shown) {
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
.pass_field input:focus + .pass-icon,
|
||||||
|
.pass_field input:not(:placeholder-shown) + .pass-icon {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.forgot_link {
|
||||||
|
color: #087A55;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.remember-me label {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
accent-color: #087A55;
|
||||||
|
|
||||||
|
}
|
||||||
|
.remember-me label input[type="checkbox"] {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper_1 .inputError {
|
||||||
|
border-color: #d93025;
|
||||||
|
background-color: #fdf2f1;
|
||||||
|
}
|
||||||
|
.errorText {
|
||||||
|
color: #d93025;
|
||||||
|
font-size: 14px;
|
||||||
|
padding-top: 6px;
|
||||||
|
position: absolute;
|
||||||
|
left: 4px;
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import { useState, type SubmitEvent, type ReactElement } from "react";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import styles from "./AuthComponent.module.css";
|
||||||
|
import { login } from "../../../api/LoginAPI/Auth";
|
||||||
|
import { persistRememberMe } from "../../../utils/rememberMe";
|
||||||
|
|
||||||
|
export function Auth():ReactElement {
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [rememberMe, setRememberMe] = useState(false);
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [errors, setErrors] = useState<{ email?: string; password?: string }>({});
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
function validateEmail(value: string) {
|
||||||
|
if (!value) return "Введите email";
|
||||||
|
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
function validatePassword(value: string) {
|
||||||
|
return value ? undefined : "Введите пароль";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit(e: SubmitEvent<HTMLFormElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
const next = { email: validateEmail(email), password: validatePassword(password) };
|
||||||
|
setErrors(next);
|
||||||
|
if (next.email || next.password) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await login(email, password);
|
||||||
|
persistRememberMe(rememberMe);
|
||||||
|
navigate("/dashboard");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setErrors({ password: "Неверный email или пароль" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.wrapper_1}>
|
||||||
|
<form noValidate onSubmit={handleSubmit}>
|
||||||
|
<h1 className={styles.Title}> Добро Пожаловать <br /> в The DisExcel Project </h1>
|
||||||
|
<p className={styles.Description}>Войдите в систему, чтобы продолжить работу с вашими процессами</p>
|
||||||
|
<label className={styles.Fields} htmlFor="email">Email</label>
|
||||||
|
<div className={styles.email_field}>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
autoComplete="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
onBlur={() => setErrors((p) => ({ ...p, email: validateEmail(email) }))}
|
||||||
|
placeholder="Email"
|
||||||
|
aria-invalid={!!errors.email}
|
||||||
|
className={errors.email ? styles.inputError : undefined}
|
||||||
|
/>
|
||||||
|
<svg className={styles["email-icon"]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<rect x="3" y="5" width="18" height="14" rx="2.5" stroke="#72879A" strokeWidth="1.8"/>
|
||||||
|
<path d="m4.5 7 7.5 6 7.5-6" stroke="#72879A" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
{errors.email && <p className={styles.errorText}>{errors.email}</p>}
|
||||||
|
</div>
|
||||||
|
<label className={styles.Fields} htmlFor="password">Password</label>
|
||||||
|
<div className={styles.pass_field}>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
autoComplete="current-password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
onBlur={() => setErrors((p) => ({ ...p, password: validatePassword(password) }))}
|
||||||
|
placeholder="Password"
|
||||||
|
aria-invalid={!!errors.password}
|
||||||
|
className={errors.password ? styles.inputError : undefined}
|
||||||
|
/>
|
||||||
|
<svg className={styles["pass-icon"]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<rect x="4" y="10" width="16" height="11" rx="2.5" stroke="#72879A" strokeWidth="1.8"/>
|
||||||
|
<path d="M8 10V7a4 4 0 0 1 8 0v3" stroke="#72879A" strokeWidth="1.8" strokeLinecap="round"/>
|
||||||
|
</svg>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles["toggle-password"]}
|
||||||
|
onClick={() => setShowPassword((v) => !v)}
|
||||||
|
aria-label={showPassword ? "Скрыть пароль" : "Показать пароль"}
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M3 3l18 18" stroke="#72879A" strokeWidth="1.8" strokeLinecap="round"/>
|
||||||
|
<path d="M10.6 5.2A10.6 10.6 0 0 1 12 5c5 0 9 4 10 7-.4 1.1-1.2 2.4-2.3 3.6M6.4 6.4C4.4 7.8 2.9 9.7 2 12c1 3 5 7 10 7 1.4 0 2.7-.3 3.9-.8" stroke="#72879A" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M9.9 10a3 3 0 0 0 4.2 4.2" stroke="#72879A" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M2 12c1-3 5-7 10-7s9 4 10 7c-1 3-5 7-10 7s-9-4-10-7Z" stroke="#72879A" strokeWidth="1.8" strokeLinejoin="round"/>
|
||||||
|
<circle cx="12" cy="12" r="3" stroke="#72879A" strokeWidth="1.8"/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
{errors.password && <p className={styles.errorText}>{errors.password}</p>}
|
||||||
|
</div>
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<div className={styles["remember-me"]}>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={rememberMe}
|
||||||
|
onChange={(e) => setRememberMe(e.target.checked)}
|
||||||
|
/>
|
||||||
|
Запомнить меня
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Link to="/forgot-password" className={styles.forgot_link}>Забыли пароль?</Link>
|
||||||
|
</div>
|
||||||
|
<div className={styles.submitbtn}>
|
||||||
|
<button className={styles.submit} type="submit">
|
||||||
|
Войти
|
||||||
|
<svg className={styles["submit-arrow"]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M4 12h14" stroke="white" strokeWidth="2" strokeLinecap="round"/>
|
||||||
|
<path d="m13 6 6 6-6 6" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
.wrapper_1 form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: whitesmoke;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 24px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.wrapper_1 input {
|
||||||
|
line-height: 40px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid rgba(68, 68, 68, 0.3);
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
.Title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
line-height: 45px;
|
||||||
|
letter-spacing: 1.2px;
|
||||||
|
}
|
||||||
|
.Description {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
.Fields {
|
||||||
|
line-height: 40px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.wrapper_1 {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
.email_field {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.email_field input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 15px 12px 45px;
|
||||||
|
transition: padding-left 0.2s ease;
|
||||||
|
}
|
||||||
|
.email_field input:focus,
|
||||||
|
.email_field input:not(:placeholder-shown) {
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
.email_field input:focus + .email-icon,
|
||||||
|
.email_field input:not(:placeholder-shown) + .email-icon {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.email-icon {
|
||||||
|
height: 25px;
|
||||||
|
position: absolute;
|
||||||
|
left: 15px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
.submitbtn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 10%;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
.submit {
|
||||||
|
background-color: #087A55;
|
||||||
|
height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: larger;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.submit-arrow {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.login_link {
|
||||||
|
color: #087A55;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper_1 .inputError {
|
||||||
|
border-color: #d93025;
|
||||||
|
background-color: #fdf2f1;
|
||||||
|
}
|
||||||
|
.errorText {
|
||||||
|
color: #d93025;
|
||||||
|
font-size: 14px;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 4px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import styles from "./ResetComponent.module.css";
|
||||||
|
import { useState, type ReactElement, type SubmitEvent } from "react";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { requestPasswordReset } from "../../../api/ResetAPI/Reset";
|
||||||
|
|
||||||
|
export function Reset():ReactElement {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [errors, setErrors] = useState<{ email?: string;}>({});
|
||||||
|
|
||||||
|
function validateEmail(value: string) {
|
||||||
|
if (!value) return "Введите email";
|
||||||
|
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
async function handleSubmit(e: SubmitEvent<HTMLFormElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
const next = {
|
||||||
|
email: validateEmail(email),
|
||||||
|
};
|
||||||
|
setErrors(next);
|
||||||
|
if (next.email) return;
|
||||||
|
try {
|
||||||
|
await requestPasswordReset(email);
|
||||||
|
navigate("/");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setErrors({ email: "Ошибка, попробуйте позже" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.wrapper_1}>
|
||||||
|
<form noValidate onSubmit={handleSubmit}>
|
||||||
|
<h1 className={styles.Title}> Восстановление пароля </h1>
|
||||||
|
<p className={styles.Description}>Введите Email, и мы отправим на него новый пароль</p>
|
||||||
|
<label className={styles.Fields} htmlFor="email">Email</label>
|
||||||
|
<div className={styles.email_field}>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
autoComplete="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
onBlur={() => setErrors((p) => ({ ...p, email: validateEmail(email) }))}
|
||||||
|
placeholder="Email"
|
||||||
|
aria-invalid={!!errors.email}
|
||||||
|
className={errors.email ? styles.inputError : undefined}
|
||||||
|
/>
|
||||||
|
<svg className={styles["email-icon"]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<rect x="3" y="5" width="18" height="14" rx="2.5" stroke="#72879A" strokeWidth="1.8"/>
|
||||||
|
<path d="m4.5 7 7.5 6 7.5-6" stroke="#72879A" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
{errors.email && <p className={styles.errorText}>{errors.email}</p>}
|
||||||
|
</div>
|
||||||
|
<div className={styles.submitbtn}>
|
||||||
|
<button className={styles.submit} type="submit">
|
||||||
|
Отправить новый пароль
|
||||||
|
<svg className={styles["submit-arrow"]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M4 12h14" stroke="white" strokeWidth="2" strokeLinecap="round"/>
|
||||||
|
<path d="m13 6 6 6-6 6" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<Link to="/" className={styles.login_link}>Вернуться ко входу</Link>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { ReactElement } from "react";
|
||||||
|
import styles from "./Grid.module.css"
|
||||||
|
|
||||||
|
export function Grid():ReactElement {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.grid}>
|
||||||
|
<p>Content is here</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
.profile {
|
||||||
|
display: grid;
|
||||||
|
justify-content: end;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.profile_avatar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgb(0, 66, 33);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.menu{
|
||||||
|
position: absolute;
|
||||||
|
top:100%;
|
||||||
|
right: 0;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.menu button {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.profile_button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.text_profile {
|
||||||
|
}
|
||||||
|
.profile_name {
|
||||||
|
text-align: start;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.profile_post {
|
||||||
|
color: #444;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
.arrow_down {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { useState, type ReactElement } from "react";
|
||||||
|
import styles from "./Header.module.css"
|
||||||
|
import { logout } from "../../../api/token";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
export function Header():ReactElement {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
function toggleMenu() {
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogout() {
|
||||||
|
await logout();
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.profile}>
|
||||||
|
<button onClick={toggleMenu} className={styles.profile_button}>
|
||||||
|
<div className={styles.profile_avatar}>
|
||||||
|
ИИ
|
||||||
|
</div>
|
||||||
|
<div className={styles.text_profile}>
|
||||||
|
<div className={styles.profile_name}>
|
||||||
|
Иван Иванов
|
||||||
|
</div>
|
||||||
|
<div className={styles.profile_post}>
|
||||||
|
Администратор
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.arrow_down}>
|
||||||
|
<svg fill="#444" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 30.727 30.727" xml:space="preserve" stroke="#444"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <path d="M29.994,10.183L15.363,24.812L0.733,10.184c-0.977-0.978-0.977-2.561,0-3.536c0.977-0.977,2.559-0.976,3.536,0 l11.095,11.093L26.461,6.647c0.977-0.976,2.559-0.976,3.535,0C30.971,7.624,30.971,9.206,29.994,10.183z"></path> </g> </g></svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{open && (<ul className={styles.menu}>
|
||||||
|
<li>Settings</li>
|
||||||
|
<li><button onClick={handleLogout}>logout</button></li>
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.right_sidebar {
|
||||||
|
display: grid;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { ReactElement } from "react";
|
||||||
|
import styles from "./Right_sidebar.module.css"
|
||||||
|
|
||||||
|
export function Right_sidebar():ReactElement {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.right_sidebar}>
|
||||||
|
<p>right_sidebar</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
.wrapper{
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
"logo logo"
|
||||||
|
"elements elements";
|
||||||
|
}
|
||||||
|
.sidebar_elements{
|
||||||
|
display: grid;
|
||||||
|
grid-area: elements;
|
||||||
|
align-content: start;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
grid-area: logo;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 30% 1fr;
|
||||||
|
padding: 20px 20px 20px 5px;
|
||||||
|
}
|
||||||
|
.l_img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.logo_name {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 10px;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6% 0;
|
||||||
|
}
|
||||||
|
.logo_title {
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
.logo_heading {
|
||||||
|
color: #444;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { ReactElement } from "react";
|
||||||
|
import styles from "./Sidebar.module.css"
|
||||||
|
import l_img from "../../../assets/LOGO.png";
|
||||||
|
|
||||||
|
export function Sidebar():ReactElement {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
{/* ссылку на главную */}
|
||||||
|
<div className={styles.logo}>
|
||||||
|
<img className={styles.l_img} src={l_img} alt="Logo" />
|
||||||
|
|
||||||
|
<p className={styles.logo_name}>
|
||||||
|
<div className={styles.logo_title}>
|
||||||
|
The DisExcel
|
||||||
|
</div>
|
||||||
|
<div className={styles.logo_heading}>
|
||||||
|
Project
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/* до сюда */}
|
||||||
|
<div className={styles.sidebar_elements}>
|
||||||
|
<button>Dashboard</button>
|
||||||
|
<button>Excel</button>
|
||||||
|
<button>Papers</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { PermissionContext } from "../contexts/PermissionContext";
|
||||||
|
|
||||||
|
export function usePermissions() {
|
||||||
|
const ctx = useContext(PermissionContext);
|
||||||
|
if (!ctx) throw new Error("usePermissions must be used inside PermissionProvider");
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap');
|
||||||
|
|
||||||
|
*{padding: 0;margin: 0;border: 0;}
|
||||||
|
*,*:before,*:after{-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;}
|
||||||
|
:focus,:active{outline: none;}
|
||||||
|
a:focus,a:active{outline: none;}
|
||||||
|
nav,footer,header,aside{display: block;}
|
||||||
|
html,body{
|
||||||
|
font-family: 'Manrope', sans-serif;
|
||||||
|
user-select: none;
|
||||||
|
font-size:100%;line-height:1;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;-webkit-text-size-adjust:100%;}
|
||||||
|
input, button, textarea { font: inherit; }
|
||||||
|
input::-ms-clear{display: none;}
|
||||||
|
button{cursor: pointer;}
|
||||||
|
button::-moz-focus-inner{padding:0;border:0;}
|
||||||
|
a,a:visited{text-decoration: none;}
|
||||||
|
a:hover{text-decoration: none;}
|
||||||
|
ul li{list-style: none;}
|
||||||
|
img{vertical-align: top;}
|
||||||
|
h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight: inherit;}
|
||||||
|
a{color:inherit;}
|
||||||
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
.wrapper {
|
||||||
|
position: absolute;
|
||||||
|
inset: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 1px 1px 20px 1px rgba(68, 68, 68, 0.5);
|
||||||
|
border-radius: 40px;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
.left_side {
|
||||||
|
width: 60%;
|
||||||
|
background-image: url("../assets/bg_login.png");
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border-right: 1px solid rgba(68, 68, 68, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 40px 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
.img_logo {
|
||||||
|
width: 133px;
|
||||||
|
height: 82px;
|
||||||
|
}
|
||||||
|
.header_top {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.header_title {
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
.header_heading {
|
||||||
|
font-size: 28px;
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
.header_bottom {
|
||||||
|
font-size: 20px;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
line-height: 30px;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
.main_item_img {
|
||||||
|
width: 75%;
|
||||||
|
height: 70%;
|
||||||
|
}
|
||||||
|
.main_items {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.main_item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.main_item_title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.main_item_p {
|
||||||
|
padding-top: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
width: 50%;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 25px;
|
||||||
|
}
|
||||||
|
.section_item > svg {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.section_items {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
padding: 7% 40px 0px 0px;
|
||||||
|
}
|
||||||
|
.section_item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
width: 25%;
|
||||||
|
line-height: 25px;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.section_line {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import styles from "./AuthLayout.module.css";
|
||||||
|
import logo from "../../assets/LOGO.png";
|
||||||
|
import img_1 from "../../assets/paper.png";
|
||||||
|
import img_2 from "../../assets/PC.png";
|
||||||
|
import img_3 from "../../assets/graphics.png";
|
||||||
|
|
||||||
|
export function AuthLayout({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<div className={styles.left_side}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.header_top}>
|
||||||
|
<img className={styles.img_logo} src={logo} alt="LOGO" />
|
||||||
|
<div className={styles.header_text}>
|
||||||
|
<div className={styles.header_title}>
|
||||||
|
The DisExcel
|
||||||
|
</div>
|
||||||
|
<div className={styles.header_heading}>
|
||||||
|
Project
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.header_bottom}>
|
||||||
|
<p>
|
||||||
|
Избавляем от ручной работы. <br />
|
||||||
|
Переводим процессы из бумаги в Excel, <br />
|
||||||
|
а из Excel - в 1С и отчеты.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div className={styles.contain}>
|
||||||
|
<div className={styles.main_items}>
|
||||||
|
{/* 1 */}
|
||||||
|
<div className={styles.main_item}>
|
||||||
|
<img className={styles.main_item_img} src={img_1} alt="img" />
|
||||||
|
<div className={styles.main_item_title}>Бумага</div>
|
||||||
|
<div className={styles.main_item_p}>
|
||||||
|
Ручная работа, ошибки, потери
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.main_i}></div>
|
||||||
|
{/* 2 */}
|
||||||
|
<div className={styles.main_item}>
|
||||||
|
<img className={styles.main_item_img} src={img_2} alt="img" />
|
||||||
|
<div className={styles.main_item_title}>Excel</div>
|
||||||
|
<div className={styles.main_item_p}>
|
||||||
|
Структура, формулы, автоматизация
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.main_i}></div>
|
||||||
|
{/* 3 */}
|
||||||
|
<div className={styles.main_item}>
|
||||||
|
<img className={styles.main_item_img} src={img_3} alt="img" />
|
||||||
|
<div className={styles.main_item_title}>1С / Отчеты</div>
|
||||||
|
<div className={styles.main_item_p}>
|
||||||
|
Готовые процессы, аналитика, контроль
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<section className={styles.section}>
|
||||||
|
<div className={styles.section_items}>
|
||||||
|
{/* 1 */}
|
||||||
|
<div className={styles.section_item}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
|
||||||
|
<circle cx="24" cy="24" r="16" stroke="#087A55" strokeWidth="3"/>
|
||||||
|
<path d="M24 14v11l7 4" stroke="#087A55" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
Экономим <br /> время
|
||||||
|
</div>
|
||||||
|
<div className={styles.section_line}></div>
|
||||||
|
{/* 2 */}
|
||||||
|
<div className={styles.section_item}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
|
||||||
|
<path d="M24 6 38 11v11c0 9-5.8 15.2-14 20-8.2-4.8-14-11-14-20V11z" stroke="#087A55" strokeWidth="3" strokeLinejoin="round"/>
|
||||||
|
<path d="m17 24 5 5 10-11" stroke="#087A55" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
Снижаем <br /> ошибки
|
||||||
|
</div>
|
||||||
|
<div className={styles.section_line}></div>
|
||||||
|
{/* 3 */}
|
||||||
|
<div className={styles.section_item}>
|
||||||
|
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke="#087A55" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.5.5 0 0 0 .12-.63l-1.92-3.32a.5.5 0 0 0-.6-.22l-2.39.96a7.03 7.03 0 0 0-1.62-.94l-.36-2.54a.5.5 0 0 0-.5-.43h-3.84a.5.5 0 0 0-.5.43l-.36 2.54c-.59.24-1.13.56-1.62.94l-2.39-.96a.5.5 0 0 0-.6.22L2.66 8.85a.5.5 0 0 0 .12.63l2.03 1.58c-.05.3-.08.63-.08.94s.02.64.07.94l-2.03 1.58a.5.5 0 0 0-.12.63l1.92 3.32a.5.5 0 0 0 .6.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54a.5.5 0 0 0 .5.43h3.84a.5.5 0 0 0 .5-.43l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96a.5.5 0 0 0 .6-.22l1.92-3.32a.5.5 0 0 0-.12-.63l-2.03-1.58Z"/>
|
||||||
|
<circle cx="12" cy="12" r="3.5" stroke="#087A55" strokeWidth="1.6"/>
|
||||||
|
</svg>
|
||||||
|
Автоматизируем <br /> процессы
|
||||||
|
</div>
|
||||||
|
<div className={styles.section_line}></div>
|
||||||
|
{/* 4 */}
|
||||||
|
<div className={styles.section_item}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
|
||||||
|
<path d="M24 6 38 11v11c0 9-5.8 15.2-14 20-8.2-4.8-14-11-14-20V11z" stroke="#087A55" strokeWidth="3" strokeLinejoin="round"/>
|
||||||
|
<path d="m17 24 5 5 10-11" stroke="#087A55" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
Делаем <br /> бизнес эффективнее
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
* {
|
||||||
|
outline: 1px solid red;
|
||||||
|
}
|
||||||
|
.wrapper {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 20% 1fr 20%;
|
||||||
|
grid-template-rows: 8.5% 1fr;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color:#F7FAF9;
|
||||||
|
grid-template-areas:
|
||||||
|
"left_sidebar header header"
|
||||||
|
"left_sidebar content right_sidebar";
|
||||||
|
}
|
||||||
|
.left_sidebar{
|
||||||
|
display: grid;
|
||||||
|
grid-area: left_sidebar;
|
||||||
|
align-content: start;
|
||||||
|
gap:10px;
|
||||||
|
background-color:#F1FAF7;
|
||||||
|
border-right: 1px solid #E1E9E6 ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: grid;
|
||||||
|
grid-area: header;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-bottom: 1px solid #E1E9E6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: grid;
|
||||||
|
grid-area: content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .wrapper_1 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 5vh 1fr;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.wrapper_2 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 70% 1fr;
|
||||||
|
height: 100%;
|
||||||
|
} */
|
||||||
|
.right_sidebar{
|
||||||
|
display: grid;
|
||||||
|
grid-area: right_sidebar;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-left: 1px solid #E1E9E6;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import styles from "./Dashboard.module.css"
|
||||||
|
import { usePermissions } from "../../hooks/usePermissions";
|
||||||
|
|
||||||
|
|
||||||
|
export function MainLayout({sidebar, header, content, right_sidebar }: { sidebar: ReactNode;header: ReactNode; content: ReactNode; right_sidebar: ReactNode }) {
|
||||||
|
//const { permissions, loading } = usePermissions();
|
||||||
|
const { loading } = usePermissions();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<div className={styles.left_sidebar}>
|
||||||
|
{sidebar}
|
||||||
|
</div>
|
||||||
|
<div className={styles.header}>
|
||||||
|
{header}
|
||||||
|
{/* {permissions.has("admin") && <div><p>test</p></div>} */}
|
||||||
|
</div>
|
||||||
|
<div className={styles.content}>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
<div className={styles.right_sidebar}>
|
||||||
|
{right_sidebar}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
|
import './index.css'
|
||||||
|
import App from './App.tsx'
|
||||||
|
import { refreshToken } from './api/RefreshAPI/Refresh.ts'
|
||||||
|
import { getRememberMe } from './utils/rememberMe.ts'
|
||||||
|
import { startRefreshTimer } from './api/token.ts'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (getRememberMe()) {
|
||||||
|
await refreshToken().catch(() => {});
|
||||||
|
console.log("Timer started");
|
||||||
|
startRefreshTimer(10*60*1000);
|
||||||
|
}
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { MainLayout } from "../layouts/Main/Dashboard";
|
||||||
|
import { Sidebar} from "../features/dashboard/components/Sidebar";
|
||||||
|
import { Header } from "../features/dashboard/components/Header";
|
||||||
|
import { Grid } from "../features/dashboard/components/Grid";
|
||||||
|
import { Right_sidebar } from "../features/dashboard/components/Right_sidebar";
|
||||||
|
export function DashboardPage() {
|
||||||
|
return (
|
||||||
|
<MainLayout
|
||||||
|
header={<Header />}
|
||||||
|
content={<Grid />}
|
||||||
|
sidebar={<Sidebar />}
|
||||||
|
right_sidebar={<Right_sidebar />}
|
||||||
|
>
|
||||||
|
</MainLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Reset } from "../features/auth/components/ResetComponent";
|
||||||
|
import { AuthLayout } from "../layouts/Auth/AuthLayout";
|
||||||
|
|
||||||
|
export function ForgotPasswordPage() {
|
||||||
|
return (
|
||||||
|
<AuthLayout>
|
||||||
|
<Reset />
|
||||||
|
</AuthLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Auth } from "../features/auth/components/AuthComponent";
|
||||||
|
import { AuthLayout } from "../layouts/Auth/AuthLayout";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { fetchUser } from "../api/UserAPI/User";
|
||||||
|
import { getAccessToken } from "../api/token";
|
||||||
|
|
||||||
|
export function LoginPage(){
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [checking, setChecking] = useState(function () {
|
||||||
|
return !!getAccessToken();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(function () {
|
||||||
|
if (!getAccessToken()) return;
|
||||||
|
|
||||||
|
async function check() {
|
||||||
|
try {
|
||||||
|
await fetchUser();
|
||||||
|
navigate("/dashboard");
|
||||||
|
} catch {
|
||||||
|
setChecking(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check();
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (checking) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
<AuthLayout>
|
||||||
|
<Auth />
|
||||||
|
</AuthLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
interface PermissionsOut {
|
||||||
|
id: number
|
||||||
|
permission: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PermissionsGroupsOut {
|
||||||
|
id: number
|
||||||
|
group: string
|
||||||
|
permissions: PermissionsOut[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserOut {
|
||||||
|
first_name: string
|
||||||
|
last_name: string
|
||||||
|
middle_name: string
|
||||||
|
email: string
|
||||||
|
|
||||||
|
direct_permissions: PermissionsOut[]
|
||||||
|
group: PermissionsGroupsOut[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserIn {
|
||||||
|
email: string
|
||||||
|
plain_password:string
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { fetchUser } from "../api/UserAPI/User";
|
||||||
|
|
||||||
|
export async function loadPermissions(): Promise<Set<string>> {
|
||||||
|
const user = await fetchUser();
|
||||||
|
|
||||||
|
const permissions = new Set<string>();
|
||||||
|
for (const p of user.direct_permissions) {
|
||||||
|
permissions.add(p.permission);
|
||||||
|
}
|
||||||
|
for (const g of user.group) {
|
||||||
|
for (const p of g.permissions) {
|
||||||
|
permissions.add(p.permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export function persistRememberMe(value:boolean):void {
|
||||||
|
if (value) {
|
||||||
|
localStorage.setItem("rememberMe", "True");
|
||||||
|
sessionStorage.removeItem("rememberMe");
|
||||||
|
} else{
|
||||||
|
sessionStorage.setItem("rememberMe", "True");
|
||||||
|
localStorage.removeItem("rememberMe");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getRememberMe():boolean {
|
||||||
|
if (localStorage.getItem("rememberMe") || sessionStorage.getItem("rememberMe")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023", "DOM"],
|
||||||
|
"module": "esnext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"allowArbitraryExtensions": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"module": "nodenext",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
envDir:"../configs",
|
||||||
|
})
|
||||||
+1285
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ from contextlib import asynccontextmanager
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from src.cache.redis_client import redis_client
|
from src.cache.redis_client import redis_client
|
||||||
from src.database.users.crud import Seed
|
from src.database.users.crud import Seed
|
||||||
@@ -10,6 +11,7 @@ from src.logging.http_logger import LoggingMiddleware, ProcessingTimeMiddleware
|
|||||||
from src.logging.logger import LogWriter
|
from src.logging.logger import LogWriter
|
||||||
from src.messaging.rabbitmq_client import rabbitmq_client
|
from src.messaging.rabbitmq_client import rabbitmq_client
|
||||||
from src.messaging.topology_setup import apply_topology
|
from src.messaging.topology_setup import apply_topology
|
||||||
|
from src.models.configs_read.env import env_settings
|
||||||
from src.models.rabbitmq_models.email import email_topology
|
from src.models.rabbitmq_models.email import email_topology
|
||||||
from src.web.protected_routes.auth_routes import router as protected_router
|
from src.web.protected_routes.auth_routes import router as protected_router
|
||||||
from src.web.protected_routes.protected_user_action_routes import (
|
from src.web.protected_routes.protected_user_action_routes import (
|
||||||
@@ -35,6 +37,13 @@ app.add_middleware(LoggingMiddleware)
|
|||||||
app.add_middleware(ProcessingTimeMiddleware)
|
app.add_middleware(ProcessingTimeMiddleware)
|
||||||
app.include_router(router=protected_router)
|
app.include_router(router=protected_router)
|
||||||
app.include_router(router=protected_user_action_routes)
|
app.include_router(router=protected_user_action_routes)
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=[f"http://{env_settings.FRONT_HOST}:{env_settings.FRONT_PORT}"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("")
|
@app.get("")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
VENV:=source .venv/bin/activate;
|
VENV:=source .venv/bin/activate &&
|
||||||
ALLURE:=.venv/allure-2.44.0/bin/allure #linux&macos
|
ALLURE:=.venv/allure-2.44.0/bin/allure #linux&macos
|
||||||
#ALLURE=.venv\allure-2.44.0\bin\allure #Windows
|
#ALLURE=.venv\allure-2.44.0\bin\allure #Windows
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ help:
|
|||||||
##
|
##
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run: ## Run dev local application
|
run: ## Run dev local application
|
||||||
docker compose -f docker/compose-dev.yaml --env-file configs/.env --profile local up -d && ${VENV} uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
docker compose -f docker/compose-dev.yaml --env-file configs/.env --profile local up -d && $(MAKE) -j2 backend frontend
|
||||||
|
|
||||||
.PHONY: down
|
.PHONY: down
|
||||||
down: ## Down dev local db
|
down: ## Down dev local db
|
||||||
@@ -39,6 +39,14 @@ down-dev: ## Down dev application
|
|||||||
down-prod: ## Down prod application
|
down-prod: ## Down prod application
|
||||||
docker compose -f docker/compose-dev.yaml --env-file configs/.env --profile prod down
|
docker compose -f docker/compose-dev.yaml --env-file configs/.env --profile prod down
|
||||||
|
|
||||||
|
.PHONY: backend
|
||||||
|
backend: ## Run backend only
|
||||||
|
${VENV} uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
|
||||||
|
.PHONY: frontend
|
||||||
|
frontend: ## Run frontend only
|
||||||
|
yarn --cwd ./frontend dev
|
||||||
|
|
||||||
##
|
##
|
||||||
## Migration section
|
## Migration section
|
||||||
##
|
##
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
"$basedir/node" "$basedir/../yarn/bin/yarn.js" "$@"
|
||||||
|
ret=$?
|
||||||
|
else
|
||||||
|
node "$basedir/../yarn/bin/yarn.js" "$@"
|
||||||
|
ret=$?
|
||||||
|
fi
|
||||||
|
exit $ret
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
@IF EXIST "%~dp0\node.exe" (
|
||||||
|
"%~dp0\node.exe" "%~dp0\..\yarn\bin\yarn.js" %*
|
||||||
|
) ELSE (
|
||||||
|
@SETLOCAL
|
||||||
|
@SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
node "%~dp0\..\yarn\bin\yarn.js" %*
|
||||||
|
)
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
"$basedir/node" "$basedir/../yarn/bin/yarn.js" "$@"
|
||||||
|
ret=$?
|
||||||
|
else
|
||||||
|
node "$basedir/../yarn/bin/yarn.js" "$@"
|
||||||
|
ret=$?
|
||||||
|
fi
|
||||||
|
exit $ret
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
@IF EXIST "%~dp0\node.exe" (
|
||||||
|
"%~dp0\node.exe" "%~dp0\..\yarn\bin\yarn.js" %*
|
||||||
|
) ELSE (
|
||||||
|
@SETLOCAL
|
||||||
|
@SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
node "%~dp0\..\yarn\bin\yarn.js" %*
|
||||||
|
)
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"systemParams": "win32-x64-137",
|
||||||
|
"modulesFolders": [
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"flags": [],
|
||||||
|
"linkedModules": [],
|
||||||
|
"topLevelPatterns": [
|
||||||
|
"yarn@^1.22.22"
|
||||||
|
],
|
||||||
|
"lockfileEntries": {
|
||||||
|
"yarn@^1.22.22": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz"
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"artifacts": {}
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
BSD 2-Clause License
|
||||||
|
|
||||||
|
For Yarn software
|
||||||
|
|
||||||
|
Copyright (c) 2016-present, Yarn Contributors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://yarnpkg.com/">
|
||||||
|
<img alt="Yarn" src="https://github.com/yarnpkg/assets/blob/master/yarn-kitten-full.png?raw=true" width="546">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Fast, reliable, and secure dependency management.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://circleci.com/gh/yarnpkg/yarn"><img alt="Circle Status" src="https://circleci.com/gh/yarnpkg/yarn.svg?style=shield&circle-token=5f0a78473b0f440afb218bf2b82323cc6b3cb43f"></a>
|
||||||
|
<a href="https://ci.appveyor.com/project/kittens/yarn/branch/master"><img alt="Appveyor Status" src="https://ci.appveyor.com/api/projects/status/0xdv8chwe2kmk463?svg=true"></a>
|
||||||
|
<a href="https://dev.azure.com/yarnpkg/yarn/_build"><img alt="Azure Pipelines status" src="https://dev.azure.com/yarnpkg/yarn/_apis/build/status/Yarn%20Acceptance%20Tests"></a>
|
||||||
|
<a href="https://discord.gg/yarnpkg"><img alt="Discord Chat" src="https://img.shields.io/discord/226791405589233664.svg"></a>
|
||||||
|
<a href="http://commitizen.github.io/cz-cli/"><img alt="Commitizen friendly" src="https://img.shields.io/badge/commitizen-friendly-brightgreen.svg"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Fast:** Yarn caches every package it has downloaded, so it never needs to download the same package again. It also does almost everything concurrently to maximize resource utilization. This means even faster installs.
|
||||||
|
|
||||||
|
**Reliable:** Using a detailed but concise lockfile format and a deterministic algorithm for install operations, Yarn is able to guarantee that any installation that works on one system will work exactly the same on another system.
|
||||||
|
|
||||||
|
**Secure:** Yarn uses checksums to verify the integrity of every installed package before its code is executed.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* **Offline Mode.** If you've installed a package before, then you can install it again without an internet connection.
|
||||||
|
* **Deterministic.** The same dependencies will be installed in the same exact way on any machine, regardless of installation order.
|
||||||
|
* **Network Performance.** Yarn efficiently queues requests and avoids request waterfalls in order to maximize network utilization.
|
||||||
|
* **Network Resilience.** A single request that fails will not cause the entire installation to fail. Requests are automatically retried upon failure.
|
||||||
|
* **Flat Mode.** Yarn resolves mismatched versions of dependencies to a single version to avoid creating duplicates.
|
||||||
|
* **More emojis.** 🐈
|
||||||
|
|
||||||
|
## Installing Yarn
|
||||||
|
|
||||||
|
Read the [Installation Guide](https://yarnpkg.com/en/docs/install) on our website for detailed instructions on how to install Yarn.
|
||||||
|
|
||||||
|
## Using Yarn
|
||||||
|
|
||||||
|
Read the [Usage Guide](https://yarnpkg.com/en/docs/usage) on our website for detailed instructions on how to use Yarn.
|
||||||
|
|
||||||
|
## Contributing to Yarn
|
||||||
|
|
||||||
|
Contributions are always welcome, no matter how large or small. Substantial feature requests should be proposed as an [RFC](https://github.com/yarnpkg/rfcs). Before contributing, please read the [code of conduct](CODE_OF_CONDUCT.md).
|
||||||
|
|
||||||
|
See [Contributing](https://yarnpkg.com/org/contributing/).
|
||||||
|
|
||||||
|
## Prior art
|
||||||
|
|
||||||
|
Yarn wouldn't exist if it wasn't for excellent prior art. Yarn has been inspired by the following projects:
|
||||||
|
|
||||||
|
- [Bundler](https://github.com/bundler/bundler)
|
||||||
|
- [Cargo](https://github.com/rust-lang/cargo)
|
||||||
|
- [npm](https://github.com/npm/cli)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Thanks to [Sam Holmes](https://github.com/samholmes) for donating the npm package name!
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
argv0=$(echo "$0" | sed -e 's,\\,/,g')
|
||||||
|
basedir=$(dirname "$(readlink "$0" || echo "$argv0")")
|
||||||
|
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin) basedir="$( cd "$( dirname "$argv0" )" && pwd )";;
|
||||||
|
Linux) basedir=$(dirname "$(readlink -f "$0" || echo "$argv0")");;
|
||||||
|
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||||
|
*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
command_exists() {
|
||||||
|
command -v "$1" >/dev/null 2>&1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if command_exists node; then
|
||||||
|
if [ "$YARN_FORCE_WINPTY" = 1 ] || command_exists winpty && test -t 1; then
|
||||||
|
winpty node "$basedir/yarn.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/yarn.js" "$@"
|
||||||
|
fi
|
||||||
|
ret=$?
|
||||||
|
# Debian and Ubuntu use "nodejs" as the name of the binary, not "node", so we
|
||||||
|
# search for that too. See:
|
||||||
|
# https://lists.debian.org/debian-devel-announce/2012/07/msg00002.html
|
||||||
|
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=614907
|
||||||
|
elif command_exists nodejs; then
|
||||||
|
exec nodejs "$basedir/yarn.js" "$@"
|
||||||
|
ret=$?
|
||||||
|
else
|
||||||
|
>&2 echo 'Yarn requires Node.js 4.0 or higher to be installed.'
|
||||||
|
ret=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $ret
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
node "%~dp0\yarn.js" %*
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/* eslint-disable no-var */
|
||||||
|
/* eslint-disable flowtype/require-valid-file-annotation */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var ver = process.versions.node;
|
||||||
|
var majorVer = parseInt(ver.split('.')[0], 10);
|
||||||
|
|
||||||
|
if (majorVer < 4) {
|
||||||
|
console.error('Node version ' + ver + ' is not supported, please use Node.js 4.0 or higher.');
|
||||||
|
process.exit(1); // eslint-disable-line no-process-exit
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
require(__dirname + '/../lib/v8-compile-cache.js');
|
||||||
|
} catch (err) {
|
||||||
|
// We don't have/need this on legacy builds and dev builds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just requiring this package will trigger a yarn run since the
|
||||||
|
// `require.main === module` check inside `cli/index.js` will always
|
||||||
|
// be truthy when built with webpack :(
|
||||||
|
// `lib/cli` may be `lib/cli/index.js` or `lib/cli.js` depending on the build.
|
||||||
|
var cli = require(__dirname + '/../lib/cli');
|
||||||
|
if (!cli.autoRun) {
|
||||||
|
cli.default().catch(function(error) {
|
||||||
|
console.error(error.stack || error.message || error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
require('./yarn.js');
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
"%~dp0\yarn.cmd" %*
|
||||||
+154071
File diff suppressed because one or more lines are too long
+351
@@ -0,0 +1,351 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Module = require('module');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const vm = require('vm');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// FileSystemBlobStore
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class FileSystemBlobStore {
|
||||||
|
constructor(directory, prefix) {
|
||||||
|
const name = prefix ? slashEscape(prefix + '.') : '';
|
||||||
|
this._blobFilename = path.join(directory, name + 'BLOB');
|
||||||
|
this._mapFilename = path.join(directory, name + 'MAP');
|
||||||
|
this._lockFilename = path.join(directory, name + 'LOCK');
|
||||||
|
this._directory = directory;
|
||||||
|
this._load();
|
||||||
|
}
|
||||||
|
|
||||||
|
has(key, invalidationKey) {
|
||||||
|
if (hasOwnProperty.call(this._memoryBlobs, key)) {
|
||||||
|
return this._invalidationKeys[key] === invalidationKey;
|
||||||
|
} else if (hasOwnProperty.call(this._storedMap, key)) {
|
||||||
|
return this._storedMap[key][0] === invalidationKey;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key, invalidationKey) {
|
||||||
|
if (hasOwnProperty.call(this._memoryBlobs, key)) {
|
||||||
|
if (this._invalidationKeys[key] === invalidationKey) {
|
||||||
|
return this._memoryBlobs[key];
|
||||||
|
}
|
||||||
|
} else if (hasOwnProperty.call(this._storedMap, key)) {
|
||||||
|
const mapping = this._storedMap[key];
|
||||||
|
if (mapping[0] === invalidationKey) {
|
||||||
|
return this._storedBlob.slice(mapping[1], mapping[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, invalidationKey, buffer) {
|
||||||
|
this._invalidationKeys[key] = invalidationKey;
|
||||||
|
this._memoryBlobs[key] = buffer;
|
||||||
|
this._dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(key) {
|
||||||
|
if (hasOwnProperty.call(this._memoryBlobs, key)) {
|
||||||
|
this._dirty = true;
|
||||||
|
delete this._memoryBlobs[key];
|
||||||
|
}
|
||||||
|
if (hasOwnProperty.call(this._invalidationKeys, key)) {
|
||||||
|
this._dirty = true;
|
||||||
|
delete this._invalidationKeys[key];
|
||||||
|
}
|
||||||
|
if (hasOwnProperty.call(this._storedMap, key)) {
|
||||||
|
this._dirty = true;
|
||||||
|
delete this._storedMap[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isDirty() {
|
||||||
|
return this._dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
const dump = this._getDump();
|
||||||
|
const blobToStore = Buffer.concat(dump[0]);
|
||||||
|
const mapToStore = JSON.stringify(dump[1]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mkdirpSync(this._directory);
|
||||||
|
fs.writeFileSync(this._lockFilename, 'LOCK', {flag: 'wx'});
|
||||||
|
} catch (error) {
|
||||||
|
// Swallow the exception if we fail to acquire the lock.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(this._blobFilename, blobToStore);
|
||||||
|
fs.writeFileSync(this._mapFilename, mapToStore);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
fs.unlinkSync(this._lockFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_load() {
|
||||||
|
try {
|
||||||
|
this._storedBlob = fs.readFileSync(this._blobFilename);
|
||||||
|
this._storedMap = JSON.parse(fs.readFileSync(this._mapFilename));
|
||||||
|
} catch (e) {
|
||||||
|
this._storedBlob = Buffer.alloc(0);
|
||||||
|
this._storedMap = {};
|
||||||
|
}
|
||||||
|
this._dirty = false;
|
||||||
|
this._memoryBlobs = {};
|
||||||
|
this._invalidationKeys = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDump() {
|
||||||
|
const buffers = [];
|
||||||
|
const newMap = {};
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
function push(key, invalidationKey, buffer) {
|
||||||
|
buffers.push(buffer);
|
||||||
|
newMap[key] = [invalidationKey, offset, offset + buffer.length];
|
||||||
|
offset += buffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of Object.keys(this._memoryBlobs)) {
|
||||||
|
const buffer = this._memoryBlobs[key];
|
||||||
|
const invalidationKey = this._invalidationKeys[key];
|
||||||
|
push(key, invalidationKey, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of Object.keys(this._storedMap)) {
|
||||||
|
if (hasOwnProperty.call(newMap, key)) continue;
|
||||||
|
const mapping = this._storedMap[key];
|
||||||
|
const buffer = this._storedBlob.slice(mapping[1], mapping[2]);
|
||||||
|
push(key, mapping[0], buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [buffers, newMap];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// NativeCompileCache
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class NativeCompileCache {
|
||||||
|
constructor() {
|
||||||
|
this._cacheStore = null;
|
||||||
|
this._previousModuleCompile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCacheStore(cacheStore) {
|
||||||
|
this._cacheStore = cacheStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
install() {
|
||||||
|
const self = this;
|
||||||
|
this._previousModuleCompile = Module.prototype._compile;
|
||||||
|
Module.prototype._compile = function(content, filename) {
|
||||||
|
const mod = this;
|
||||||
|
function require(id) {
|
||||||
|
return mod.require(id);
|
||||||
|
}
|
||||||
|
require.resolve = function(request) {
|
||||||
|
return Module._resolveFilename(request, mod);
|
||||||
|
};
|
||||||
|
require.main = process.mainModule;
|
||||||
|
|
||||||
|
// Enable support to add extra extension types
|
||||||
|
require.extensions = Module._extensions;
|
||||||
|
require.cache = Module._cache;
|
||||||
|
|
||||||
|
const dirname = path.dirname(filename);
|
||||||
|
|
||||||
|
const compiledWrapper = self._moduleCompile(filename, content);
|
||||||
|
|
||||||
|
// We skip the debugger setup because by the time we run, node has already
|
||||||
|
// done that itself.
|
||||||
|
|
||||||
|
const args = [mod.exports, require, mod, filename, dirname, process, global];
|
||||||
|
return compiledWrapper.apply(mod.exports, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall() {
|
||||||
|
Module.prototype._compile = this._previousModuleCompile;
|
||||||
|
}
|
||||||
|
|
||||||
|
_moduleCompile(filename, content) {
|
||||||
|
// https://github.com/nodejs/node/blob/v7.5.0/lib/module.js#L511
|
||||||
|
|
||||||
|
// Remove shebang
|
||||||
|
var contLen = content.length;
|
||||||
|
if (contLen >= 2) {
|
||||||
|
if (content.charCodeAt(0) === 35/*#*/ &&
|
||||||
|
content.charCodeAt(1) === 33/*!*/) {
|
||||||
|
if (contLen === 2) {
|
||||||
|
// Exact match
|
||||||
|
content = '';
|
||||||
|
} else {
|
||||||
|
// Find end of shebang line and slice it off
|
||||||
|
var i = 2;
|
||||||
|
for (; i < contLen; ++i) {
|
||||||
|
var code = content.charCodeAt(i);
|
||||||
|
if (code === 10/*\n*/ || code === 13/*\r*/) break;
|
||||||
|
}
|
||||||
|
if (i === contLen) {
|
||||||
|
content = '';
|
||||||
|
} else {
|
||||||
|
// Note that this actually includes the newline character(s) in the
|
||||||
|
// new output. This duplicates the behavior of the regular
|
||||||
|
// expression that was previously used to replace the shebang line
|
||||||
|
content = content.slice(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create wrapper function
|
||||||
|
var wrapper = Module.wrap(content);
|
||||||
|
|
||||||
|
var invalidationKey = crypto
|
||||||
|
.createHash('sha1')
|
||||||
|
.update(content, 'utf8')
|
||||||
|
.digest('hex');
|
||||||
|
|
||||||
|
var buffer = this._cacheStore.get(filename, invalidationKey);
|
||||||
|
|
||||||
|
var script = new vm.Script(wrapper, {
|
||||||
|
filename: filename,
|
||||||
|
lineOffset: 0,
|
||||||
|
displayErrors: true,
|
||||||
|
cachedData: buffer,
|
||||||
|
produceCachedData: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (script.cachedDataProduced) {
|
||||||
|
this._cacheStore.set(filename, invalidationKey, script.cachedData);
|
||||||
|
} else if (script.cachedDataRejected) {
|
||||||
|
this._cacheStore.delete(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
var compiledWrapper = script.runInThisContext({
|
||||||
|
filename: filename,
|
||||||
|
lineOffset: 0,
|
||||||
|
columnOffset: 0,
|
||||||
|
displayErrors: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return compiledWrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// utilities
|
||||||
|
//
|
||||||
|
// https://github.com/substack/node-mkdirp/blob/f2003bb/index.js#L55-L98
|
||||||
|
// https://github.com/zertosh/slash-escape/blob/e7ebb99/slash-escape.js
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function mkdirpSync(p_) {
|
||||||
|
_mkdirpSync(path.resolve(p_), parseInt('0777', 8) & ~process.umask());
|
||||||
|
}
|
||||||
|
|
||||||
|
function _mkdirpSync(p, mode) {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(p, mode);
|
||||||
|
} catch (err0) {
|
||||||
|
if (err0.code === 'ENOENT') {
|
||||||
|
_mkdirpSync(path.dirname(p));
|
||||||
|
_mkdirpSync(p);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const stat = fs.statSync(p);
|
||||||
|
if (!stat.isDirectory()) { throw err0; }
|
||||||
|
} catch (err1) {
|
||||||
|
throw err0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function slashEscape(str) {
|
||||||
|
const ESCAPE_LOOKUP = {
|
||||||
|
'\\': 'zB',
|
||||||
|
':': 'zC',
|
||||||
|
'/': 'zS',
|
||||||
|
'\x00': 'z0',
|
||||||
|
'z': 'zZ',
|
||||||
|
};
|
||||||
|
return str.replace(/[\\:\/\x00z]/g, match => (ESCAPE_LOOKUP[match]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function supportsCachedData() {
|
||||||
|
const script = new vm.Script('""', {produceCachedData: true});
|
||||||
|
// chakracore, as of v1.7.1.0, returns `false`.
|
||||||
|
return script.cachedDataProduced === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCacheDir() {
|
||||||
|
// Avoid cache ownership issues on POSIX systems.
|
||||||
|
const dirname = typeof process.getuid === 'function'
|
||||||
|
? 'v8-compile-cache-' + process.getuid()
|
||||||
|
: 'v8-compile-cache';
|
||||||
|
const version = typeof process.versions.v8 === 'string'
|
||||||
|
? process.versions.v8
|
||||||
|
: typeof process.versions.chakracore === 'string'
|
||||||
|
? 'chakracore-' + process.versions.chakracore
|
||||||
|
: 'node-' + process.version;
|
||||||
|
const cacheDir = path.join(os.tmpdir(), dirname, version);
|
||||||
|
return cacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParentName() {
|
||||||
|
// `module.parent.filename` is undefined or null when:
|
||||||
|
// * node -e 'require("v8-compile-cache")'
|
||||||
|
// * node -r 'v8-compile-cache'
|
||||||
|
// * Or, requiring from the REPL.
|
||||||
|
const parentName = module.parent && typeof module.parent.filename === 'string'
|
||||||
|
? module.parent.filename
|
||||||
|
: process.cwd();
|
||||||
|
return parentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// main
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if (!process.env.DISABLE_V8_COMPILE_CACHE && supportsCachedData()) {
|
||||||
|
const cacheDir = getCacheDir();
|
||||||
|
const prefix = getParentName();
|
||||||
|
const blobStore = new FileSystemBlobStore(cacheDir, prefix);
|
||||||
|
|
||||||
|
const nativeCompileCache = new NativeCompileCache();
|
||||||
|
nativeCompileCache.setCacheStore(blobStore);
|
||||||
|
nativeCompileCache.install();
|
||||||
|
|
||||||
|
process.once('exit', code => {
|
||||||
|
if (blobStore.isDirty()) {
|
||||||
|
blobStore.save();
|
||||||
|
}
|
||||||
|
nativeCompileCache.uninstall();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.__TEST__ = {
|
||||||
|
FileSystemBlobStore,
|
||||||
|
NativeCompileCache,
|
||||||
|
mkdirpSync,
|
||||||
|
slashEscape,
|
||||||
|
supportsCachedData,
|
||||||
|
getCacheDir,
|
||||||
|
getParentName,
|
||||||
|
};
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "yarn",
|
||||||
|
"installationMethod": "tar",
|
||||||
|
"version": "1.22.22",
|
||||||
|
"packageManager": "yarn@1.22.17",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"preferGlobal": true,
|
||||||
|
"description": "📦🐈 Fast, reliable, and secure dependency management.",
|
||||||
|
"resolutions": {
|
||||||
|
"sshpk": "^1.14.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
},
|
||||||
|
"repository": "yarnpkg/yarn",
|
||||||
|
"bin": {
|
||||||
|
"yarn": "./bin/yarn.js",
|
||||||
|
"yarnpkg": "./bin/yarn.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"preinstall": ":; (node ./preinstall.js > /dev/null 2>&1 || true)"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"commitizen": {
|
||||||
|
"path": "./node_modules/cz-conventional-changelog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
// This file is a bit weird, so let me explain with some context: we're working
|
||||||
|
// to implement a tool called "Corepack" in Node. This tool will allow us to
|
||||||
|
// provide a Yarn shim to everyone using Node, meaning that they won't need to
|
||||||
|
// run `npm install -g yarn`.
|
||||||
|
//
|
||||||
|
// Still, we don't want to break the experience of people that already use `npm
|
||||||
|
// install -g yarn`! And one annoying thing with npm is that they install their
|
||||||
|
// binaries directly inside the Node bin/ folder. And Because of this, they
|
||||||
|
// refuse to overwrite binaries when they detect they don't belong to npm. Which
|
||||||
|
// means that, since the "yarn" Corepack symlink belongs to Corepack and not npm,
|
||||||
|
// running `npm install -g yarn` would crash by refusing to override the binary :/
|
||||||
|
//
|
||||||
|
// And thus we have this preinstall script, which checks whether Yarn is being
|
||||||
|
// installed as a global binary, and remove the existing symlink if it detects
|
||||||
|
// it belongs to Corepack. Since preinstall scripts run, in npm, before the global
|
||||||
|
// symlink is created, we bypass this way the ownership check.
|
||||||
|
//
|
||||||
|
// More info:
|
||||||
|
// https://github.com/arcanis/pmm/issues/6
|
||||||
|
|
||||||
|
if (process.env.npm_config_global) {
|
||||||
|
var cp = require('child_process');
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
try {
|
||||||
|
var targetPath = cp.execFileSync(process.execPath, [process.env.npm_execpath, 'bin', '-g'], {
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: ['ignore', undefined, 'ignore'],
|
||||||
|
}).replace(/\n/g, '');
|
||||||
|
|
||||||
|
var manifest = require('./package.json');
|
||||||
|
var binNames = typeof manifest.bin === 'string'
|
||||||
|
? [manifest.name.replace(/^@[^\/]+\//, '')]
|
||||||
|
: typeof manifest.bin === 'object' && manifest.bin !== null
|
||||||
|
? Object.keys(manifest.bin)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
binNames.forEach(function (binName) {
|
||||||
|
var binPath = path.join(targetPath, binName);
|
||||||
|
|
||||||
|
var binTarget;
|
||||||
|
try {
|
||||||
|
binTarget = fs.readlinkSync(binPath);
|
||||||
|
} catch (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binTarget.startsWith('../lib/node_modules/corepack/')) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(binPath);
|
||||||
|
} catch (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
// ignore errors
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+26
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "The_DisExcel_project",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"yarn": "^1.22.22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yarn": {
|
||||||
|
"version": "1.22.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz",
|
||||||
|
"integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"bin": {
|
||||||
|
"yarn": "bin/yarn.js",
|
||||||
|
"yarnpkg": "bin/yarn.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"yarn": "^1.22.22"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+873
-1
@@ -707,6 +707,77 @@ all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>
|
|||||||
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "fastar (>=0.9.0)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "fastar (>=0.9.0)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "graphifyy"
|
||||||
|
version = "0.9.63"
|
||||||
|
description = "AI coding assistant skill (Claude Code, CodeBuddy, Codex, OpenCode, Kilo Code, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Pi, Devin CLI, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "graphifyy-0.9.63-py3-none-any.whl", hash = "sha256:e56aff02d01021c3337b192af6e4d1d2d28b70a35bb4912f777264c3858712d3"},
|
||||||
|
{file = "graphifyy-0.9.63.tar.gz", hash = "sha256:0d2073903f6cc79a67eae27e1cb9284323da6b29555ecef5c2e35b7a4a1d2a5b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
networkx = ">=3.4"
|
||||||
|
numpy = ">=1.21"
|
||||||
|
rapidfuzz = ">=3.0"
|
||||||
|
tree-sitter = ">=0.23.0,<0.26"
|
||||||
|
tree-sitter-bash = ">=0.23,<0.27"
|
||||||
|
tree-sitter-c = ">=0.23,<0.25"
|
||||||
|
tree-sitter-c-sharp = ">=0.23,<0.25"
|
||||||
|
tree-sitter-cpp = ">=0.23,<0.25"
|
||||||
|
tree-sitter-elixir = ">=0.3,<0.5"
|
||||||
|
tree-sitter-fortran = ">=0.6,<0.8"
|
||||||
|
tree-sitter-go = ">=0.23,<0.26"
|
||||||
|
tree-sitter-groovy = ">=0.1,<0.3"
|
||||||
|
tree-sitter-java = ">=0.23,<0.25"
|
||||||
|
tree-sitter-javascript = ">=0.23,<0.26"
|
||||||
|
tree-sitter-json = ">=0.23,<0.26"
|
||||||
|
tree-sitter-julia = ">=0.23,<0.25"
|
||||||
|
tree-sitter-kotlin = ">=1.0,<2.0"
|
||||||
|
tree-sitter-lua = ">=0.2,<0.6"
|
||||||
|
tree-sitter-objc = ">=3.0,<4.0"
|
||||||
|
tree-sitter-php = ">=0.23,<0.25"
|
||||||
|
tree-sitter-powershell = ">=0.26,<0.28"
|
||||||
|
tree-sitter-python = ">=0.23,<0.26"
|
||||||
|
tree-sitter-ruby = ">=0.23,<0.25"
|
||||||
|
tree-sitter-rust = ">=0.23,<0.25"
|
||||||
|
tree-sitter-scala = ">=0.23,<0.27"
|
||||||
|
tree-sitter-swift = ">=0.7,<0.9"
|
||||||
|
tree-sitter-typescript = ">=0.23,<0.25"
|
||||||
|
tree-sitter-verilog = ">=1.0,<2.0"
|
||||||
|
tree-sitter-zig = ">=1.0,<2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["anthropic", "boto3", "falkordb", "faster-whisper ; python_version >= \"3.11\"", "graspologic ; python_version < \"3.13\"", "graspologic-native (>=1.3.1,<2) ; python_version >= \"3.13\"", "jieba ; python_version < \"3.12\"", "jieba-py (>=0.46.12,<1) ; python_version >= \"3.12\"", "markdownify", "matplotlib", "mcp (>=1,<3)", "neo4j", "numpy (>=2.0) ; python_version >= \"3.13\"", "openai", "openpyxl", "psycopg[binary]", "pypdf (>=6.16.1)", "python-docx", "robotframework (>=4.0)", "starlette (>=1.3.1,<2)", "tiktoken", "tree-sitter-commonlisp", "tree-sitter-dm", "tree-sitter-hcl", "tree-sitter-ocaml", "tree-sitter-pascal", "tree-sitter-sql", "watchdog", "yt-dlp (>=2026.7.4)"]
|
||||||
|
anthropic = ["anthropic"]
|
||||||
|
bedrock = ["boto3"]
|
||||||
|
chinese = ["jieba ; python_version < \"3.12\"", "jieba-py (>=0.46.12,<1) ; python_version >= \"3.12\""]
|
||||||
|
commonlisp = ["tree-sitter-commonlisp"]
|
||||||
|
dm = ["tree-sitter-dm"]
|
||||||
|
falkordb = ["falkordb"]
|
||||||
|
gemini = ["openai", "tiktoken"]
|
||||||
|
google = ["openpyxl"]
|
||||||
|
kimi = ["openai", "tiktoken"]
|
||||||
|
leiden = ["graspologic ; python_version < \"3.13\"", "graspologic-native (>=1.3.1,<2) ; python_version >= \"3.13\""]
|
||||||
|
mcp = ["mcp (>=1,<3)", "starlette (>=1.3.1,<2)"]
|
||||||
|
neo4j = ["neo4j"]
|
||||||
|
ocaml = ["tree-sitter-ocaml"]
|
||||||
|
office = ["openpyxl", "python-docx"]
|
||||||
|
ollama = ["openai"]
|
||||||
|
openai = ["openai", "tiktoken"]
|
||||||
|
pascal = ["tree-sitter-pascal"]
|
||||||
|
pdf = ["markdownify", "pypdf (>=6.16.1)"]
|
||||||
|
postgres = ["psycopg[binary]", "tree-sitter-sql"]
|
||||||
|
robot = ["robotframework (>=4.0)"]
|
||||||
|
sql = ["tree-sitter-sql"]
|
||||||
|
svg = ["matplotlib", "numpy (>=2.0) ; python_version >= \"3.13\""]
|
||||||
|
terraform = ["tree-sitter-hcl"]
|
||||||
|
video = ["faster-whisper ; python_version >= \"3.11\"", "yt-dlp (>=2026.7.4)"]
|
||||||
|
watch = ["watchdog"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "greenlet"
|
name = "greenlet"
|
||||||
version = "3.5.4"
|
version = "3.5.4"
|
||||||
@@ -1352,6 +1423,105 @@ files = [
|
|||||||
{file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"},
|
{file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "networkx"
|
||||||
|
version = "3.6"
|
||||||
|
description = "Python package for creating and manipulating graphs and networks"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.11"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "networkx-3.6-py3-none-any.whl", hash = "sha256:cdb395b105806062473d3be36458d8f1459a4e4b98e236a66c3a48996e07684f"},
|
||||||
|
{file = "networkx-3.6.tar.gz", hash = "sha256:285276002ad1f7f7da0f7b42f004bcba70d381e936559166363707fdad3d72ad"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
benchmarking = ["asv", "virtualenv"]
|
||||||
|
default = ["matplotlib (>=3.8)", "numpy (>=1.25)", "pandas (>=2.0)", "scipy (>=1.11.2)"]
|
||||||
|
developer = ["mypy (>=1.15)", "pre-commit (>=4.1)"]
|
||||||
|
doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=10)", "pydata-sphinx-theme (>=0.16)", "sphinx (>=8.0)", "sphinx-gallery (>=0.18)", "texext (>=0.6.7)"]
|
||||||
|
example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "iplotx (>=0.9.0)", "momepy (>=0.7.2)", "osmnx (>=2.0.0)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"]
|
||||||
|
extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"]
|
||||||
|
release = ["build (>=0.10)", "changelist (==0.5)", "twine (>=4.0)", "wheel (>=0.40)"]
|
||||||
|
test = ["pytest (>=7.2)", "pytest-cov (>=4.0)", "pytest-xdist (>=3.0)"]
|
||||||
|
test-extras = ["pytest-mpl", "pytest-randomly"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numpy"
|
||||||
|
version = "2.5.3"
|
||||||
|
description = "Fundamental package for array computing in Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.12"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9"},
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c"},
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0"},
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e"},
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58"},
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3"},
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff"},
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034"},
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-win32.whl", hash = "sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4"},
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def"},
|
||||||
|
{file = "numpy-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-win32.whl", hash = "sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec"},
|
||||||
|
{file = "numpy-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:350ba9783ce969cf9f7ce6e6a9a58e1a6e2a19ca025b7ee448c4db727706212a"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:012e66aca395d795496446e52aeeb5866312a5d4d3f27da270e5a0b43f70dc5c"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:adc1ada2662f8a5f960b8a10d9986897e7499ef07e06d4cfe7197f8cce923c07"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:54a115e5a73b8fc44f0cebef486365a1894b5c9760685d4558b72b7c3eb846e0"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be5a8381859b6da607c84f4f7d6847725f1cf1853ef8a2c9e115b7d58bef47dc"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0521d0f4aebb6e06189451025fa17a913287b13c03d5fe05c017333b654ea5b"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9deb49575e5b0b94ed72c8a64ec4d033381adc27e9060ae842971f697ba96104"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b00eefbcf0f292945c4b4dec2ae845389ef5bcdcd596e6e4328051db5b5ba694"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-win32.whl", hash = "sha256:c2381f82999704f818e2c987a865050e285ec3621262c66d40f5a96c8f899f8e"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:2c25dfa72943e4336ddb6b0ee4277b47a0c85bede0807530ec68103bf58e2c10"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:15aa985ac73a8db02db7663381aa109510449d3819d37206caed27b33a65a8a6"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ac7bb1c52d445bd4f8f7f97fefe6abc3a084dc4d63df50d79b17fa2b78e89297"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e6ab667ba76450084eb64013762c438ea76d9d29cc676dcd6c2e9892ba37f841"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f7fabeb6cea87d65f3b926de33d03fb016cfdc29314c90974383b5582ae72891"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fb6f8fb9ff0b3a69f52c66ce397b0246583e9f28616231b0e32ca49259a5fa6"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93e1f5447e2b1e479d7bd74701e84746b86450cff1fc368b132d195e2b8f8211"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c00abe94c1a69d75d827dcf1c025b25c8a45d230b3bcd77a9020883a1b047653"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:536f963710a4e63934d80ac0dc4f478804a83e9a84b6828018f25d09953ada33"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314t-win32.whl", hash = "sha256:4c8a6d2ebce6305fd82fbefca827775437147052a976ee7c94b36a0c1b52ac6c"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9a37475425b431b4d060f23b4f52cd2f3aef6bc7c654bd760adf0040eec9d435"},
|
||||||
|
{file = "numpy-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2d8240cb4c16fd831074aa2b2cf9fc54664d826341d61c372245b96a74a49a9a"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a6391fafaba97500887132cd582abc6e19452b1ac775a47caa7b24490e152058"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:09d5a423c71ad5feb5625844ad58050e35df43871004b52ac9c0ad44a56775be"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:f9579f383d1bf9df80081e72760e84960a7fd4f88cf0c9e535a8597c9bb646f5"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:86bff898a431c0fb71f7610b75726e75a54d47b37edc9d537f48de63bb3c0b90"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f3ed25271581281f2fccb1adcedfcde4c07362eec69189b50baf6f90e3ae159"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffdc76bfcae6b255dff75202c5e7feaf95b40246bc0a17944facc1fecf9f79ab"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:116f96cadd935c6122e9228d676fe7ede19e741f5c8bb1c3cddbe0c51ccebea2"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:09ffa5d903faeaa5c4dd05009cf81c8bab9f2cb37c548b8d39b65b4cfa7c97f7"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-win32.whl", hash = "sha256:e01c918ac3d48e18a927cf7b14a26a3e29ff2bdf2eacb976da0aecd6a43ed034"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:e931e4f499e0dc7ef29d269a8e5b35dd722e5d14be07df6240166ea7c6532fae"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:26e15e4aecd8617dfbaecb37d223e365d7b39411fba20454be2670a96aa74cb5"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:6cef4bb1706dfec49243c05d921eefb4e190d41e2528b30d8035ea1f36b4c24a"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d1c89973648c85069c5046ad460f7b8a00218b29a2e42359ac8cc63e9ab94832"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:214045a5bf00113a146ab9ee9730c44501af6723cdf1f6830932f7b5ef2e7af0"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:8617bbfae4486cf99c9f899966699428d19da931d06ca94ad3da986c76e15997"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:595d020938c84e320bcf40ad71089e108eac0d377cd018e14a8c094f39e98d85"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f24021b9f22bc6301c37b196974a92c1c18dccedb6fef3dd252e95f2d6adbe4"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:71b39d9f935b6ec0f8753e3e2afb51e3efba6f2e05b68b32a40754d24bcd4a3c"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6b05c171afb3aa07adbd20abc00aea86fe375beb0fdb9ef780ec5b7f63bab1c0"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-win32.whl", hash = "sha256:f54660b0eb6b0b9f36e7fe1cdfdff472028dd0d14acd9b9b65098efbad059469"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:1aad64d99730d013cfc6debafed22783b4fc5a7f4b8bc744d2d8cf7dcc880551"},
|
||||||
|
{file = "numpy-2.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:befa1ae5bd6030b3f512b43ff3fa5290bbed6b84411a44244b14adf835f5b89d"},
|
||||||
|
{file = "numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "26.2"
|
version = "26.2"
|
||||||
@@ -2075,6 +2245,122 @@ files = [
|
|||||||
{file = "python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e"},
|
{file = "python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rapidfuzz"
|
||||||
|
version = "3.14.6"
|
||||||
|
description = "rapid fuzzy string matching"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.11"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c0dd0d765184366b6e213a8af3b0b3bb39dad27943bbfb193515d4ff96ac82a"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0c61cade182f130c9903231946bd1074539121721693a918e7b70382ae802bd8"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3781cf14f9fc933d7198c2b25a8bbbd1a62b752746d5cd26de14957edc0e802f"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71a5bbfd00da1963f27dd1432068929694cf0e00007ae2b9c1ad2a187ec29a16"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:eabaf06ca4896c59cfd9162480f0d37a15a2304ce2efe83ae2bbcfa1cf13534e"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d5d90bae3c6fb7ea34da968c9f23070e8440edb827a28b242580e0108110b14"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:d6b58daadbe6974884ec39aee30cfb8bd2e126f8d03503f0069f70d5e84656a3"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ab4386ef7c2cb3e5eb46e815be49715dfcd301bb9f0a431f18da7aa0007de54f"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:33a2f7faedaa3608c4876c41b448fc786d54e6cd7c6e732f7de466319b5a73c2"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:adb160a100f6122aa45c78d686e198da3f9e815d4182e0c4fe730608479f7f9c"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ad60297c001d15af24338440bca85dfee8710e9e3222733c906b33e89d986166"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d5b1cfa67bbe6239a643bca1d986f8a07e0a045286c674946e1648c132baa46"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-win32.whl", hash = "sha256:46ddb42af4cad3ac9d5e0c97ee1e687500c529a1ad5cbf9c949ce35f6edd4537"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-win_amd64.whl", hash = "sha256:737a57cbca3e5c16decac86e205727bcd4b99c52f77c48bb44123078c5cd9a7a"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp311-cp311-win_arm64.whl", hash = "sha256:19c1cda8198cc57ffd4ff69a1c02cbe4297e9ca7b506bca03ec584da0a9fe1ff"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b46cecf27025e7a934332ade033e6a394da8a493f19fa1d835e3b2968a4ff7da"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1901414b135afb1a7f4b1ef940b95523b49cc5642aecf02af740f37567e98137"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96a548979cd939b2c69358a0f5088a408524fbf7454f04bf90939fa971e64310"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b22ef7e5e2341efc6216b666491022027b984e5aef93446064742f43f3c1d926"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f0d2d95c787d812b9106cfbcb94ad37a49f59df9287e00a75eb61afc246e8759"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0debb5f43662ea84d2f0228a0c7407ff647f9c3d13f3b692efff0cde46eebce0"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:1d253e1fe44648242a0029b42ba23adf238ed2a7eb3d8ed0a03731a23f074ae0"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e06c6050c9bf6cd72305e3e6a293918b2b92cf2a067007585a53898624902e3c"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d85a6e9180e53cde95c95dfeb05a2ac94ead4d9d803a8fd186d2719a678b8483"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:35db2670f69fa3a4eb4741055581477ff92f2cf39e7e06f43ebcb97c2192fe7c"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f9d93e5424d1e4c103b57906b8beba270e680afda3ffdff7ea3bc6173b37083c"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9b0a501f37fb852c54469375baa25874246b3bbc8b6e21fb4cd186a32335868"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-win32.whl", hash = "sha256:9e974251a9833791bc557b46f975676a56c2d58946f795cd2964b095496dfdcc"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:cfca36e4612208875e08611a779164b6cb8900ab8bbd3d82d4cfdfae9efbfac9"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp312-cp312-win_arm64.whl", hash = "sha256:96bbd5a1c67d135334d02fae74f1d933fdda204ea03d544a59dab6b1cbfbf565"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:55dc9a55924b4ecfcf4a60a701bcfae7d9daf0129c41dc16139270d75be0996c"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bba0e9fad4dbea80227cde9cef3aaa984a934a84aec5f7505532e19838b14769"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b34b7ee4f4f760690d6477163aabbec05705b5dd764cb6c3a6ba95aa1fffc42"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abe92a70134c8b40790bb5c78b2a0a790686c26e83b6e99a456127ca141fe06a"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:659b41570fcc6e02631ac361c47cc8db9ad26d740e4be2177df1b63005a49174"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb896f89a387219c671ebc33c4a636b222010cc3c5c83884a7fc8707bf0bbf9"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:11d76bb2b2cd038df708ae18f521fb3a50af477cc5a0dffce812da43a2f1beb3"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:28e9ce91bd41a8203185887ef9b1541a891aa61c5c1cb2e46f1689cd4288d372"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:864658e5a10d249a2277374e800f944fe990346d70eea6f3a51b712b6dd01984"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:3c2444f5cd757ded2c3ba8b1734253b801b9b2ba9ecb3ee40cd505cebbfa7341"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2cc9b5dde0ac89f7856f997ef917cac8e18e9dea473e9b3090a84bd600de6a91"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:faebff9b9a287fb673f9a66465a7e03043601c9bfe5e71c3f91b3f2e7b8a37f6"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-win32.whl", hash = "sha256:4406b2517b85febcf9419f8fbcdfbd534872ea32608050f9562224933ca49a4c"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-win_amd64.whl", hash = "sha256:c69fb0e064d10c79908dcda76d7ca8ecdf8393a39acbb74dbad3f709f2c60e95"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp313-cp313-win_arm64.whl", hash = "sha256:a0c8bef04f6b1d9fdbb319576350af53151a64692d477db7d4844c220bc8e212"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f8d6718e7edacdb16455c0472e7552fd518decb91e91250c58784fd6163f54f"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8fa7d45388dec34a86038f2a38380f4922b74b5dd8991247f629a531178db10f"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:760ee152af5e8b4d241a469f933ba2d7215248618ae19770fec7d80d9e149db6"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbe3378db3ae0453accf6196e2ed943f43d416cfacdcb8883db105bc14a0130f"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9ddb0ddf3ee616fdc066add4ef05639c5cf59b58d83779b6023488e5435f6191"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:08bc63b88048376114d1e66cf8fa6926495d03bb873eb87854fa74cf6848a70b"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:50cd6718bcda7ec5293635a9d0b3fb5906251013d3b99ca403ba9dfa8965f661"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:63b0e84faec3c5706cae8ae51246ff103407d54efa32a615a548b7b67392ebcf"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9080a730fdcf3cb8a07464c90f9cf40c1b4ffc73a8375b56a8898aba619dda30"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:178557c7a50c8c8d65369ede7f3d845bf23590a951c9a368caf166b105d58cf3"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:44f1cddbc2010700e2d88063d0ab64183efe2578d9b52770ce1cd283dda230c5"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:17081a0e904c12bb4ed49619a2bbb6528f6af00fe850e7ace22487bfd2aea455"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-win32.whl", hash = "sha256:9e00c8c9500aacbc0c52b66369f54533ecbdcb92e5aa87e160fc8e293000a696"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-win_amd64.whl", hash = "sha256:41ee893c4d7d0fb1844f6cad966540a833784b3bad2c239a0d80195d9231cef4"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314-win_arm64.whl", hash = "sha256:10576c39fe6a49fad0bf1069371a77300ce166a3f36d2900d2d0bae08f297104"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1b0a9546a7328d3cfc2f1385501db7c4c374fb566dc1a3b22ad56092846c0134"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9989280902b9c4ecf7de95fbb906e94df0d8c047290ed315c7aa1760cec9b3de"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc166efa4ca2fc9cc52e43784a54cbea95fc0e03e533f8266ef66b1c04c7cb76"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:32352a3ed1aad9c097d31fd4f2eece3030169e2de3dedde7a2fadc2652b768ad"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ecb45d616002751b58914d5b7c2e66acd39e12242be12717a1258148a1b36526"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f9ad513e3a3e045b60b421d5cd3887ae0a33b38fc6c6db3ea5e27c0a2e0412c"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:f35723caef8cc31b6f34209708fb172fc88bab0077c12e9b36bbb829baaf1b16"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:408b2e8e8c1ac71b57f0923cf964d6932539725e07b69e70ec66f22c4a403891"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5667c56fdc902fa1e12449b5c042e8b1c7e9b30040db20c396fbdb3d0a750866"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:76a122fc573df603deb5fb827df31bb5efbd0826b50bb7aeca8535a6e8c70cf9"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:e221366e24709b9d41d5f9cc99053b04cfc575d429e956a82cfbc4c4e9e8860a"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:36710ff214b7a8049d26a9c81d99948026593cacb47663742c4119072b651ecd"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-win32.whl", hash = "sha256:66ece6f5e2586c742fc3e0b8487e06783d27c6c24adcdcfdd7f306afbd8b5737"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cab4a932cec02d09471e2c9f1434049ef5bfe1f6e646ff10939c222dc610ad60"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b056ce19eaea2ea70c6a6fb387a605ca2af8979de5b9d507597e8012820ddb14"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:bc3d74d18543ddfbc8babe1faadb19927a7999fd0d01181cce9e721c14c36ab6"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:aaa83b633d877a05d549d2073629134998d1b3b9dbc114873d3ff4277984979f"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbe6a62f71fcbca72acbf5a30e53380600369f257f951d664d81d30c0c598595"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b82c21c30568e096ef2a9dda7d45c379e6141694e0472dac73bc4372ce13ccee"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:fc950bb77105a2717d03d9f9c9e21e9ace7df2b8e864dd91edef7e32fa143be2"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:c53a269bdbd71ffbc856d3db9e609478251001ee272507578fa838bc2bd421fe"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:bf4fb0f19c9dfce7a908c3e309753602ce3edb83bb74e9ff997e278765bf89df"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:189ce2bf14938bfa003fbbe7e6da7584ed6ebbc4c560686255dbc20e2829f470"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-win32.whl", hash = "sha256:7ca0f498bf771a87557e6d8b573aa6cf3daded58ae2eaeb6973618ce3e1615ad"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-win_amd64.whl", hash = "sha256:d4c5adb921b67dd79ffc0a14f92b9f8df3d012e66aab340b154ed87014229d93"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315-win_arm64.whl", hash = "sha256:c9d135fb93709d707577da8a7a8ffc7283525a5b6d0ce55aa3724be5639ed65b"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:dd89abd1c4b3776c3471a817216830bd275441c8344bbda5d51a3bffe1e0fbdf"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:eab2d4680d7f438dbb1d484b187d59a943edea9c83f792c764a0c148a417a60a"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8683fefdd3484d64a191b3efbc8cbe9162c3eac891fd62d0a1b70e117ffcd434"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2bc7af3a699371a941aac86dc8a79ac92adeb3c2add2aab02230e76068a0029e"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:40c2753e2d4dc96b25f8a25adc23ab0bb6cfd8bc8125a1753ac4b037d6ff6511"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:36a37ddc729c33618d89fa221d3333b9b956dc38cf15d31301e6169d962399a3"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:635f242f4bdf05d1477fa409815bd73e5f78896773ace84997bc472ffeef685f"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:40d0cd9c82083aeb30bae8dee265ae571e6748d0d7b222ddd777f33d95a3b712"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-win32.whl", hash = "sha256:15da2b258908eb38853c1a6a58a1d09d9aad9c721e03a68c8ba691cd31dff739"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-win_amd64.whl", hash = "sha256:3d502769263318690d4f6638b08483979d1b88cdc7c6f087482eea935fde4031"},
|
||||||
|
{file = "rapidfuzz-3.14.6-cp315-cp315t-win_arm64.whl", hash = "sha256:07c7aa0b1e4b9999a54f9e73317d6743ff85442c8ef7b7fbbe6b190fd37d9e75"},
|
||||||
|
{file = "rapidfuzz-3.14.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0844066900cdc9909ce4ab4fb5ba1d8e0c021252d770f2ea476f3443df1d22ef"},
|
||||||
|
{file = "rapidfuzz-3.14.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1398bd2c197b79bfc40b615999fd3599dc60265fdd5b59edc18156ae048c4cde"},
|
||||||
|
{file = "rapidfuzz-3.14.6-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2fc748d1fde4109e5d0dab27f1e61f53b3136a235dfee5a4fb579da44808b6a"},
|
||||||
|
{file = "rapidfuzz-3.14.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b42536675c930cb76b7998bfc4d8e59cb35d8df47f2103020265743b6b2ccd2a"},
|
||||||
|
{file = "rapidfuzz-3.14.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1e6911e3a14971719ddc35af98f181d2e5369ab273a5a3488ab7685d23c31ad5"},
|
||||||
|
{file = "rapidfuzz-3.14.6.tar.gz", hash = "sha256:e13a8160d017b499ec7a2fa9d0ce1ae2e7377080815785819f966fb235d4eb60"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["numpy"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "8.1.0"
|
version = "8.1.0"
|
||||||
@@ -2375,6 +2661,592 @@ files = [
|
|||||||
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
|
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
|
||||||
test = ["argcomplete (>=3.0.3)", "mypy (>=1.17.0,<1.19)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
|
test = ["argcomplete (>=3.0.3)", "mypy (>=1.17.0,<1.19)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter"
|
||||||
|
version = "0.25.2"
|
||||||
|
description = "Python bindings to the Tree-sitter parsing library"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72a510931c3c25f134aac2daf4eb4feca99ffe37a35896d7150e50ac3eee06c7"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44488e0e78146f87baaa009736886516779253d6d6bac3ef636ede72bc6a8234"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2f8e7d6b2f8489d4a9885e3adcaef4bc5ff0a275acd990f120e29c4ab3395c5"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b570690f87f1da424cd690e51cc56728d21d63f4abd4b326d382a30353acc7"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a0ec41b895da717bc218a42a3a7a0bfcfe9a213d7afaa4255353901e0e21f696"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:7712335855b2307a21ae86efe949c76be36c6068d76df34faa27ce9ee40ff444"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp310-cp310-win_arm64.whl", hash = "sha256:a925364eb7fbb9cdce55a9868f7525a1905af512a559303bd54ef468fd88cb37"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ca72d841215b6573ed0655b3a5cd1133f9b69a6fa561aecad40dca9029d75b"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc0351cfe5022cec5a77645f647f92a936b38850346ed3f6d6babfbeeeca4d26"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1799609636c0193e16c38f366bda5af15b1ce476df79ddaae7dd274df9e44266"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e65ae456ad0d210ee71a89ee112ac7e72e6c2e5aac1b95846ecc7afa68a194c"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:49ee3c348caa459244ec437ccc7ff3831f35977d143f65311572b8ba0a5f265f"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:56ac6602c7d09c2c507c55e58dc7026b8988e0475bd0002f8a386cce5e8e8adc"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp311-cp311-win_arm64.whl", hash = "sha256:b3d11a3a3ac89bb8a2543d75597f905a9926f9c806f40fcca8242922d1cc6ad5"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721"},
|
||||||
|
{file = "tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinx (>=8.1,<9.0)", "sphinx-book-theme"]
|
||||||
|
tests = ["tree-sitter-html (>=0.23.2)", "tree-sitter-javascript (>=0.23.1)", "tree-sitter-json (>=0.24.8)", "tree-sitter-python (>=0.23.6)", "tree-sitter-rust (>=0.23.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-bash"
|
||||||
|
version = "0.25.1"
|
||||||
|
description = "Bash grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_bash-0.25.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0e6235f59e366d220dde7d830196bed597d01e853e44d8ccd1a82c5dd2500acf"},
|
||||||
|
{file = "tree_sitter_bash-0.25.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f4a34a6504c7c5b2a9b8c5c4065531dea19ca2c35026e706cf2eeeebe2c92512"},
|
||||||
|
{file = "tree_sitter_bash-0.25.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e76c4cfb20b076552406782b7f8c2a3946835993df0a44df006de54b7030c7dc"},
|
||||||
|
{file = "tree_sitter_bash-0.25.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f484c4bb8796cde7a87ca351e6116f09653edac0eb3c6d238566359dd28b117"},
|
||||||
|
{file = "tree_sitter_bash-0.25.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5e76af6df46d958c7f5b6d5884c9743218e3902a00ccb493ec92728b1084430b"},
|
||||||
|
{file = "tree_sitter_bash-0.25.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a3332d71c7b7d5f78259b19d02d0ea111fcb82b72712ee4a93aaa5b226d3f0a8"},
|
||||||
|
{file = "tree_sitter_bash-0.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:52a6802d9218f86278aa3e8b459c3abdad67eed0fde1f9f13aca5b6c634217a6"},
|
||||||
|
{file = "tree_sitter_bash-0.25.1-cp310-abi3-win_arm64.whl", hash = "sha256:59115057ec2bae319e8082ff29559861045002964c3431ccb0fc92aa4bc9bccb"},
|
||||||
|
{file = "tree_sitter_bash-0.25.1.tar.gz", hash = "sha256:bfc0bdaa77bc1e86e3c6652e5a6e140c40c0a16b84185c2b63ad7cd809b88f14"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.24,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-c"
|
||||||
|
version = "0.24.2"
|
||||||
|
description = "C grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_c-0.24.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4d4579a8b54f0a442f903d88d3304cab77cd5c2031d4015baa4f2f8e15d6dcb7"},
|
||||||
|
{file = "tree_sitter_c-0.24.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:97bc80a224d48215d4e6e6376bf30d114f4c317b8145ff1b02afe785d4ba7bdd"},
|
||||||
|
{file = "tree_sitter_c-0.24.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5041ef67eb68ce6bc8bb0b1f8ef3a5585ce523dae0c7eec109ab0627dd75aede"},
|
||||||
|
{file = "tree_sitter_c-0.24.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c098bedcd5ac86ff93fa734d51d1dd86aed40fd5ed7d634c7af11380a0469969"},
|
||||||
|
{file = "tree_sitter_c-0.24.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:82842c5a5f2acd93f4de10038c33ac179c8979defc39376f990348d6289e933b"},
|
||||||
|
{file = "tree_sitter_c-0.24.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2b42e8e22202c251f8629306f9321233542e07a6e01611b5fe83489272143eb"},
|
||||||
|
{file = "tree_sitter_c-0.24.2-cp310-abi3-win_amd64.whl", hash = "sha256:abb549225091f7b25df2dd3a0143ece6e208f7055d8bcb4700b41ee79b9ef1e1"},
|
||||||
|
{file = "tree_sitter_c-0.24.2-cp310-abi3-win_arm64.whl", hash = "sha256:4a2f4371cd816cc3153458f69062135ebb2ea5f275ddd90494e5c823d778204a"},
|
||||||
|
{file = "tree_sitter_c-0.24.2.tar.gz", hash = "sha256:1628584df0299b5a340aa63f8e67b6c97c91517f52fa7e7a4c557e40adb330a9"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.24,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-c-sharp"
|
||||||
|
version = "0.23.5"
|
||||||
|
description = "C# grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_c_sharp-0.23.5-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61e1981cf21b09ee547b9c4c68e64fb4394325f8fc8d5f6d50d41471eba923ea"},
|
||||||
|
{file = "tree_sitter_c_sharp-0.23.5-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a75994a11f6fed3f5b8c36ad6a00e5dc43205bd912c43af3a2a54fdf649664eb"},
|
||||||
|
{file = "tree_sitter_c_sharp-0.23.5-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aa88a780204cd153c4c1ae2d59c654cee1402212fa0d069823d6d34301587438"},
|
||||||
|
{file = "tree_sitter_c_sharp-0.23.5-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea38fb095d85d360dc5a0bec2fa605e496228876f798c9e089d5f0e72bcef46"},
|
||||||
|
{file = "tree_sitter_c_sharp-0.23.5-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:05a9256415e7f24d4f133133794a9c224c60d19f677a04e2f6a94c25090b6d65"},
|
||||||
|
{file = "tree_sitter_c_sharp-0.23.5-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8636dc70b5a373c35c1036ed5de98e801f2e4d105ae41e2e20b6804c36e3bf33"},
|
||||||
|
{file = "tree_sitter_c_sharp-0.23.5-cp310-abi3-win_amd64.whl", hash = "sha256:41a28cfa3d9ea50f5629e44550a03188c8fbd5079803dfc03554b6fd594b33fa"},
|
||||||
|
{file = "tree_sitter_c_sharp-0.23.5-cp310-abi3-win_arm64.whl", hash = "sha256:2de4ebf95ddc2e92cd3105c8a8e0e7ec646bc82f52bfaf2f3acec0fa2401ec09"},
|
||||||
|
{file = "tree_sitter_c_sharp-0.23.5.tar.gz", hash = "sha256:2635c7d5ec93e59f2e831b571bed99c4cc68a5d183a0994020aa769e1b990a71"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-cpp"
|
||||||
|
version = "0.23.4"
|
||||||
|
description = "C++ grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_cpp-0.23.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aacb1759f0efd9dbc25bd8ee88184a340483018869f75412d9c3bc32c039a520"},
|
||||||
|
{file = "tree_sitter_cpp-0.23.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc3c404d9f0cbd87951213a85440afbf4c31e718f8d907fa9ee12bea4b8d276f"},
|
||||||
|
{file = "tree_sitter_cpp-0.23.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc43ddf1279d5d5a4ef190373f4cb16522801bec4492bcd4754edf2aeba2b7b"},
|
||||||
|
{file = "tree_sitter_cpp-0.23.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:773d2cafc08bbc0f998687fa33f42f378c1a371cdb582870c4d13abb06092706"},
|
||||||
|
{file = "tree_sitter_cpp-0.23.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:247d127f0eb6574b0f6b30c0151e0bd0774e2e7acf9c558bdf9fbb8adc2e80c0"},
|
||||||
|
{file = "tree_sitter_cpp-0.23.4-cp39-abi3-win_amd64.whl", hash = "sha256:68606a45bea92669d155399e1239f771a7767d8683cd8f8e30e7d813107030ca"},
|
||||||
|
{file = "tree_sitter_cpp-0.23.4-cp39-abi3-win_arm64.whl", hash = "sha256:712f84f18be94cbe2a148fa4fdf40fcf4a8c25a8f7670efb9f8a47ddec2fc281"},
|
||||||
|
{file = "tree_sitter_cpp-0.23.4.tar.gz", hash = "sha256:6a59c4cebb1ad1dc2e8d586cf8a72b39d21b8108b7b139d089719e81a339e41d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-elixir"
|
||||||
|
version = "0.3.5"
|
||||||
|
description = "Elixir grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_elixir-0.3.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:514078a2f68d27da9a1e6b6e9601b8456faba6260ecfa252e898a848c4f8584d"},
|
||||||
|
{file = "tree_sitter_elixir-0.3.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:015f537731af690cfa238b0fb76a8af4f0d1a2c54a38563f159926d2967ce650"},
|
||||||
|
{file = "tree_sitter_elixir-0.3.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ebfe3491a3d00ac50b12a3bfcabb1c564f3809ed8a095099fe87f49d6b3987e6"},
|
||||||
|
{file = "tree_sitter_elixir-0.3.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1159057f914d4468fc53cb9d7e8369f8a7826e1d07765bb53fbf391e6058863"},
|
||||||
|
{file = "tree_sitter_elixir-0.3.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d6187b4d592bfb31760799ac6ddbb5a2457ba0a612de43d77bcbcd5f00cc49bf"},
|
||||||
|
{file = "tree_sitter_elixir-0.3.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5d5d8aa077ff244d24406b1fb5a17c03a2919c5183c51ca35654870d08b239b"},
|
||||||
|
{file = "tree_sitter_elixir-0.3.5-cp39-abi3-win_amd64.whl", hash = "sha256:c0b5df229405d42ba5c94254d92e414b1f200be8422561d243ae5b3558e84f76"},
|
||||||
|
{file = "tree_sitter_elixir-0.3.5-cp39-abi3-win_arm64.whl", hash = "sha256:fee42b90962e1e131cc31720f3038410291b2196ed231e00c1721597fc0567df"},
|
||||||
|
{file = "tree_sitter_elixir-0.3.5.tar.gz", hash = "sha256:ead089393b1ce732304e6b6fb0bc0ab79e3295663d697be025bd49f0f367b74d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.23,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-fortran"
|
||||||
|
version = "0.6.0"
|
||||||
|
description = "Fortran grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_fortran-0.6.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b6495c4c25cf68785ffd30e615b5481219415761ca66dde14a9577d03075714d"},
|
||||||
|
{file = "tree_sitter_fortran-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:a0fe5929fd91d245aba5a3b414399a296fb9924942a549190cee226e5b1ec96c"},
|
||||||
|
{file = "tree_sitter_fortran-0.6.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fd7b179305db93ffe8435ee42f6895e76677744721707b3f2f328a92dd4f61e"},
|
||||||
|
{file = "tree_sitter_fortran-0.6.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac4800b4abc1b25e6e7ab4a3f2eae274c5b19107beb18d3a473c0f67509c7486"},
|
||||||
|
{file = "tree_sitter_fortran-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f9ba6ca864d39f5df2787ed58222ee25570c47c659df0d7b5753a8c4dc3e29d"},
|
||||||
|
{file = "tree_sitter_fortran-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9348398630d6d7e5e3588a14517f889fc0315c33b059e004d0468000db2a7206"},
|
||||||
|
{file = "tree_sitter_fortran-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:cccd5bce1cdebcf34d3a130ecf4944bc409ddc93096317e3249838ffdaf927eb"},
|
||||||
|
{file = "tree_sitter_fortran-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:45b0e226325e626101949d6aafcf0422fc210c3cf3ae9b9a2281b41f47d9cc20"},
|
||||||
|
{file = "tree_sitter_fortran-0.6.0.tar.gz", hash = "sha256:65fea540148ae431335b3920267dffaeeb157ef2b21c0716798c751f6a9e193b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.21,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-go"
|
||||||
|
version = "0.25.0"
|
||||||
|
description = "Go grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_go-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b852993063a3429a443e7bd0aa376dd7dd329d595819fabf56ac4cf9d7257b54"},
|
||||||
|
{file = "tree_sitter_go-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:503b81a2b4c31e302869a1de3a352ad0912ccab3df9ac9950197b0a9ceeabd8f"},
|
||||||
|
{file = "tree_sitter_go-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04b3b3cb4aff18e74e28d49b716c6f24cb71ddfdd66768987e26e4d0fa812f74"},
|
||||||
|
{file = "tree_sitter_go-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:148255aca2f54b90d48c48a9dbb4c7faad6cad310a980b2c5a5a9822057ed145"},
|
||||||
|
{file = "tree_sitter_go-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4d338116cdf8a6c6ff990d2441929b41323ef17c710407abe0993c13417d6aad"},
|
||||||
|
{file = "tree_sitter_go-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5608e089d2a29fa8d2b327abeb2ad1cdb8e223c440a6b0ceab0d3fa80bdeebae"},
|
||||||
|
{file = "tree_sitter_go-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:30d4ada57a223dfc2c32d942f44d284d40f3d1215ddcf108f96807fd36d53022"},
|
||||||
|
{file = "tree_sitter_go-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:d5d62362059bf79997340773d47cc7e7e002883b527a05cca829c46e40b70ded"},
|
||||||
|
{file = "tree_sitter_go-0.25.0.tar.gz", hash = "sha256:a7466e9b8d94dda94cae8d91629f26edb2d26166fd454d4831c3bf6dfa2e8d68"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.24,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-groovy"
|
||||||
|
version = "0.1.2"
|
||||||
|
description = "Groovy grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_groovy-0.1.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27adb7a4077511782dbd94a12f4635dfb52ccb88f734fe1569393e2d28b18bbd"},
|
||||||
|
{file = "tree_sitter_groovy-0.1.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:db35a5bdceb826382c7f52d33db0b2075217473f698daf77eb8d4e557a161d51"},
|
||||||
|
{file = "tree_sitter_groovy-0.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cdb4c62284f19fbfdd4900e816c3e8604672de107e4e52a8e65b663f368b4cb"},
|
||||||
|
{file = "tree_sitter_groovy-0.1.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e938e9c2cd5fdb08fd1b28d7d621d15ea959a17a4bc0b77833e07a94fe7d263"},
|
||||||
|
{file = "tree_sitter_groovy-0.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:beda8f7b0c596e20cabc75fc076a3e6e9af8318e30c1869df6a036183a8cdd33"},
|
||||||
|
{file = "tree_sitter_groovy-0.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:bb8b20e2c92a18509ad3b830aeba9f5754778903e7dfd6999c3efb3c79c43d76"},
|
||||||
|
{file = "tree_sitter_groovy-0.1.2-cp39-abi3-win_arm64.whl", hash = "sha256:1942a9a1b22e154da9bbf1b03e6b4dbec4211b1109d24bcf4c12b006cbc04037"},
|
||||||
|
{file = "tree_sitter_groovy-0.1.2.tar.gz", hash = "sha256:49b004c4ae946d3f01a602f325cd8996423e034e5b3ad36fc34a1d1e42afa8da"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-java"
|
||||||
|
version = "0.23.5"
|
||||||
|
description = "Java grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_java-0.23.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:355ce0308672d6f7013ec913dee4a0613666f4cda9044a7824240d17f38209df"},
|
||||||
|
{file = "tree_sitter_java-0.23.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:24acd59c4720dedad80d548fe4237e43ef2b7a4e94c8549b0ca6e4c4d7bf6e69"},
|
||||||
|
{file = "tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9401e7271f0b333df39fc8a8336a0caf1b891d9a2b89ddee99fae66b794fc5b7"},
|
||||||
|
{file = "tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:370b204b9500b847f6d0c5ad584045831cee69e9a3e4d878535d39e4a7e4c4f1"},
|
||||||
|
{file = "tree_sitter_java-0.23.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:aae84449e330363b55b14a2af0585e4e0dae75eb64ea509b7e5b0e1de536846a"},
|
||||||
|
{file = "tree_sitter_java-0.23.5-cp39-abi3-win_amd64.whl", hash = "sha256:1ee45e790f8d31d416bc84a09dac2e2c6bc343e89b8a2e1d550513498eedfde7"},
|
||||||
|
{file = "tree_sitter_java-0.23.5-cp39-abi3-win_arm64.whl", hash = "sha256:402efe136104c5603b429dc26c7e75ae14faaca54cfd319ecc41c8f2534750f4"},
|
||||||
|
{file = "tree_sitter_java-0.23.5.tar.gz", hash = "sha256:f5cd57b8f1270a7f0438878750d02ccc79421d45cca65ff284f1527e9ef02e38"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-javascript"
|
||||||
|
version = "0.25.0"
|
||||||
|
description = "JavaScript grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_javascript-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b70f887fb269d6e58c349d683f59fa647140c410cfe2bee44a883b20ec92e3dc"},
|
||||||
|
{file = "tree_sitter_javascript-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8264a996b8845cfce06965152a013b5d9cbb7d199bc3503e12b5682e62bb1de1"},
|
||||||
|
{file = "tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dc04ba91fc8583344e57c1f1ed5b2c97ecaaf47480011b92fbeab8dda96db75"},
|
||||||
|
{file = "tree_sitter_javascript-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:199d09985190852e0912da2b8d26c932159be314bc04952cf917ed0e4c633e6b"},
|
||||||
|
{file = "tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dfcf789064c58dc13c0a4edb550acacfc6f0f280577f1e7a00de3e89fc7f8ddc"},
|
||||||
|
{file = "tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b852d3aee8a36186dbcc32c798b11b4869f9b5041743b63b65c2ef793db7a54"},
|
||||||
|
{file = "tree_sitter_javascript-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:e5ed840f5bd4a3f0272e441d19429b26eedc257abe5574c8546da6b556865e3c"},
|
||||||
|
{file = "tree_sitter_javascript-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:622a69d677aa7f6ee2931d8c77c981a33f0ebb6d275aa9d43d3397c879a9bb0b"},
|
||||||
|
{file = "tree_sitter_javascript-0.25.0.tar.gz", hash = "sha256:329b5414874f0588a98f1c291f1b28138286617aa907746ffe55adfdcf963f38"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.24,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-json"
|
||||||
|
version = "0.24.8"
|
||||||
|
description = "JSON grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_json-0.24.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:59ac06c6db1877d0e2076bce54a5fddcdd2fc38ca778905662e80fa9ffcea2ab"},
|
||||||
|
{file = "tree_sitter_json-0.24.8-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:62b4c45b561db31436a81a3f037f71ec29049f4fc9bf5269b6ec3ebaaa35a1cd"},
|
||||||
|
{file = "tree_sitter_json-0.24.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8627f7d375fda9fc193ebee368c453f374f65c2f25c58b6fea4e6b49a7fccbc"},
|
||||||
|
{file = "tree_sitter_json-0.24.8-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85cca779872f7278f3a74eb38533d34b9c4de4fd548615e3361fa64fe350ad0a"},
|
||||||
|
{file = "tree_sitter_json-0.24.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:deeb45850dcc52990fbb52c80196492a099e3fa3512d928a390a91cf061068cc"},
|
||||||
|
{file = "tree_sitter_json-0.24.8-cp39-abi3-win_amd64.whl", hash = "sha256:e4849a03cd7197267b2688a4506a90a13568a8e0e8588080bd0212fcb38974e3"},
|
||||||
|
{file = "tree_sitter_json-0.24.8-cp39-abi3-win_arm64.whl", hash = "sha256:591e0096c882d12668b88f30d3ca6f85b9db3406910eaaab6afb6b17d65367dd"},
|
||||||
|
{file = "tree_sitter_json-0.24.8.tar.gz", hash = "sha256:ca8486e52e2d261819311d35cf98656123d59008c3b7dcf91e61d2c0c6f3120e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-julia"
|
||||||
|
version = "0.23.1"
|
||||||
|
description = "Julia grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_julia-0.23.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4bd4d8e76ab780a2de9af90cefada494cb174991d74993b6a243f28081e9432b"},
|
||||||
|
{file = "tree_sitter_julia-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8197c8d9b0cb51421aa2832f3fb539504d7b514cbb1fc79130bb1445c0b4a457"},
|
||||||
|
{file = "tree_sitter_julia-0.23.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7708a4a01831dd7cb7e6ee25146e654a0bf89077e85ffe8b5025b63a302af145"},
|
||||||
|
{file = "tree_sitter_julia-0.23.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d4f6ae938198fc0be9b6ea76313ade24fcdb89be01a791e0cc90c88fae5743d"},
|
||||||
|
{file = "tree_sitter_julia-0.23.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a8aa8e959e73158632687423f4c6c61aa52dea65a451220e3e0223b67149a046"},
|
||||||
|
{file = "tree_sitter_julia-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:13031aa4c9ac7d0665aa3ecd9fbc6f9c6afd601c68f6ae67a8eeaca01465aeed"},
|
||||||
|
{file = "tree_sitter_julia-0.23.1-cp39-abi3-win_arm64.whl", hash = "sha256:673ad3079f2328c28affbee5dbedb63c7e6dab248579aabdb813bc7b862a0261"},
|
||||||
|
{file = "tree_sitter_julia-0.23.1.tar.gz", hash = "sha256:07607c4fc902b21e6821622f56b08aa2321b921fe0644e2ab4aba1747e6c8808"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-kotlin"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "Kotlin grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_kotlin-1.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6cca5ef06d090e8494ac1d9f0aac71ed32207d412766b5df7da00d94334181a2"},
|
||||||
|
{file = "tree_sitter_kotlin-1.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:910b41a580dae00d319e555075f3886a41386d1067931b14c7de504eeae3ae2a"},
|
||||||
|
{file = "tree_sitter_kotlin-1.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:906e5444ebb01db439cb3ad65913598a4ea957b0e068aa973265926a17eb00e0"},
|
||||||
|
{file = "tree_sitter_kotlin-1.1.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92afe24b634cf914c5812af0f5c53184b1c18bdf6ee5505c83afac81f6bf6c"},
|
||||||
|
{file = "tree_sitter_kotlin-1.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5960034a5c5bcc7ccb21dc7a29e4267ac4f0ef37884f39d75695eac7f004deff"},
|
||||||
|
{file = "tree_sitter_kotlin-1.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:d4d3f330f515ba8b91da04a5335eb9ff3ce071c7b7855958912f2560f6e14976"},
|
||||||
|
{file = "tree_sitter_kotlin-1.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:e030f127a7d07952907adb9070248bd42fb86dc76fd92744727551b50e131ee7"},
|
||||||
|
{file = "tree_sitter_kotlin-1.1.0.tar.gz", hash = "sha256:322a35bdae75e25ae64dae6027be609c5422fab282084117816c4ebcda6168da"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-lua"
|
||||||
|
version = "0.5.0"
|
||||||
|
description = "Lua grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_lua-0.5.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cc4f2eb734dc9223bf96c0eeffa78a9485db207d00841e27e52c8b036f2164f7"},
|
||||||
|
{file = "tree_sitter_lua-0.5.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c14714ad395c4166566f3e4dd0cc0979411684cbcd23702e3c631c3e6eae84fd"},
|
||||||
|
{file = "tree_sitter_lua-0.5.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ec448c854fea32414a0449147d648bc5baddf7a0357008c4abe3269db35370a"},
|
||||||
|
{file = "tree_sitter_lua-0.5.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b02f057a997e618c5b1b03a5cef9dd6c2673043d396ca86edba372728f17ef53"},
|
||||||
|
{file = "tree_sitter_lua-0.5.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a048571f55a3dd30c94e2313091274338284cab23e757c181e4961c185ba9d0"},
|
||||||
|
{file = "tree_sitter_lua-0.5.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:922a5a3d0fec8af373cab504cbcd9abeeebb212d454f54163591c50c183466be"},
|
||||||
|
{file = "tree_sitter_lua-0.5.0-cp310-abi3-win_amd64.whl", hash = "sha256:ace3dd61218124ee08410a55601cb5fbbb00be3ee004b30e705cef9ef25165a9"},
|
||||||
|
{file = "tree_sitter_lua-0.5.0-cp310-abi3-win_arm64.whl", hash = "sha256:8488f3bea40779896f5771bcfcdc26900eb21e94f6658eb68a848fc37dd39221"},
|
||||||
|
{file = "tree_sitter_lua-0.5.0.tar.gz", hash = "sha256:0e46356038ccb8ce1049289104c56230003448309a335f2e353f1edc7b373552"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.24,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-objc"
|
||||||
|
version = "3.0.2"
|
||||||
|
description = "Objective-C grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_objc-3.0.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bd25b3c4ca99263c0898aa7a362a1b8d9bb642692ae9ddd357755586019b1544"},
|
||||||
|
{file = "tree_sitter_objc-3.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa8b1221d2651a51cf42e1551c0804e9f48707da70f41f3195910c599b5522b"},
|
||||||
|
{file = "tree_sitter_objc-3.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30b6f9cd49593bac50161a6de6e1b8d591b318d64b33b8bde5385faa05461084"},
|
||||||
|
{file = "tree_sitter_objc-3.0.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e71282ac9c096a966bf2fa6a4ecdbea4bd037d3e01ea4aa9bbc64d9a4c0022f6"},
|
||||||
|
{file = "tree_sitter_objc-3.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d288d5ad4951fa31eeaf39972b39b41694eec8cc70739d48e745357c2e2c4aad"},
|
||||||
|
{file = "tree_sitter_objc-3.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:f3c93e991a86e96b8996cc735a4b31b38c65820913bf5a96904d07a51a8d9423"},
|
||||||
|
{file = "tree_sitter_objc-3.0.2-cp39-abi3-win_arm64.whl", hash = "sha256:9a99d9b81a4e507bd33329be136928b3ebe424ce8b9d6b8a8339083ceb453b5b"},
|
||||||
|
{file = "tree_sitter_objc-3.0.2.tar.gz", hash = "sha256:ac55aefe8a4f3ea6f1da2a2e05372a4f37100001934e36a81e0f96c4c6252809"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-php"
|
||||||
|
version = "0.24.1"
|
||||||
|
description = "PHP grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_php-0.24.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d56e2dcf025450f84a2cdbf4b18a09e6cb88b92e9e6858e63de3d4133ab2e43e"},
|
||||||
|
{file = "tree_sitter_php-0.24.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:29759c67d4c27a68c227ed82c0b7e4699617b1bd23757d50c081f81a12b4f80d"},
|
||||||
|
{file = "tree_sitter_php-0.24.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b89832ac09f078eed2acd88598838bc51012224cbcebb916dbb6a37e74357e"},
|
||||||
|
{file = "tree_sitter_php-0.24.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a1404a30f2972498ace040b0029738b8dac45d0a12932ccb8b605eb94bafbe4"},
|
||||||
|
{file = "tree_sitter_php-0.24.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3e96f61462a960c78e5389c7ba6c16c25e66b465c763b8e63ad66423326c2fa7"},
|
||||||
|
{file = "tree_sitter_php-0.24.1-cp310-abi3-win_amd64.whl", hash = "sha256:1a1b65b72a8410d421f914ee13d38fd546a94d01cb834f69b27c78ba7589a5b5"},
|
||||||
|
{file = "tree_sitter_php-0.24.1-cp310-abi3-win_arm64.whl", hash = "sha256:56a70c5ef1bddb15f220a479b2f2edf3042c764b6c443921fbd7ca9174d664e3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.24,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-powershell"
|
||||||
|
version = "0.26.4"
|
||||||
|
description = "A Powershell grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_powershell-0.26.4-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0bf8beac7ed4501d1c52456f8ae9728ab2a5a079325548b06b1bc9746655524e"},
|
||||||
|
{file = "tree_sitter_powershell-0.26.4-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:b5dde429c9de55b75906e240d6db1cf85417e2fc0a56d7b321810c2cd4cf3f98"},
|
||||||
|
{file = "tree_sitter_powershell-0.26.4-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:56508e4ac7aad1e3b26f2ef96b8d2b60b149c4efa0c23742e91e809a11db73ee"},
|
||||||
|
{file = "tree_sitter_powershell-0.26.4-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0989b221ce6cc1dfe3bc9993d3ca1ee96f3ca62173423b9a332a61c5afa3c12"},
|
||||||
|
{file = "tree_sitter_powershell-0.26.4-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1170665958ed29abe015ad294408f15b1f76e5d52e0b96e7718ffbf340b9670c"},
|
||||||
|
{file = "tree_sitter_powershell-0.26.4-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b2222e192edba88930b89ed5e5da66c75ea21a064768a10261c5bb01e1348de8"},
|
||||||
|
{file = "tree_sitter_powershell-0.26.4-cp310-abi3-win_amd64.whl", hash = "sha256:702eadf70ec8b1fd0bbf9b4169ed58f0ee0bcab333e5103e97c0f562be299088"},
|
||||||
|
{file = "tree_sitter_powershell-0.26.4-cp310-abi3-win_arm64.whl", hash = "sha256:5651d240387d5b9cd23ae20afdd8aad17934304a1a21d4e7825e4df38e39dda6"},
|
||||||
|
{file = "tree_sitter_powershell-0.26.4.tar.gz", hash = "sha256:ffc7f7526420fe335cb78823b38bc8b0c27453eb974ca6056779e4cfefffa605"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.24,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-python"
|
||||||
|
version = "0.25.0"
|
||||||
|
description = "Python grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361"},
|
||||||
|
{file = "tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762"},
|
||||||
|
{file = "tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5"},
|
||||||
|
{file = "tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b"},
|
||||||
|
{file = "tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d"},
|
||||||
|
{file = "tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683"},
|
||||||
|
{file = "tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76"},
|
||||||
|
{file = "tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb"},
|
||||||
|
{file = "tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.24,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-ruby"
|
||||||
|
version = "0.23.1"
|
||||||
|
description = "Ruby grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_ruby-0.23.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:39f391322d2210843f07081182dbf00f8f69cfbfa4687b9575cac6d324bae443"},
|
||||||
|
{file = "tree_sitter_ruby-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:aa4ee7433bd42fac22e2dad4a3c0f332292ecf482e610316828c711a0bb7f794"},
|
||||||
|
{file = "tree_sitter_ruby-0.23.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62b36813a56006b7569db7868f6b762caa3f4e419bd0f8cf9ccbb4abb1b6254c"},
|
||||||
|
{file = "tree_sitter_ruby-0.23.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7bcd93972b4ca2803856d4fe0fbd04123ff29c4592bbb9f12a27528bd252341"},
|
||||||
|
{file = "tree_sitter_ruby-0.23.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66c65d6c2a629783ca4ab2bab539bd6f271ce6f77cacb62845831e11665b5bd3"},
|
||||||
|
{file = "tree_sitter_ruby-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:02e2c19ebefe29226c14aa63e11e291d990f5b5c20a99940ab6e7eda44e744e5"},
|
||||||
|
{file = "tree_sitter_ruby-0.23.1-cp39-abi3-win_arm64.whl", hash = "sha256:ed042007e89f2cceeb1cbdd8b0caa68af1e2ce54c7eb2053ace760f90657ac9f"},
|
||||||
|
{file = "tree_sitter_ruby-0.23.1.tar.gz", hash = "sha256:886ed200bfd1f3ca7628bf1c9fefd42421bbdba70c627363abda67f662caa21e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-rust"
|
||||||
|
version = "0.24.2"
|
||||||
|
description = "Rust grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_rust-0.24.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3620cfd12340efa43082d45df76349ff511893a9c361da2f8d6d51e307020a59"},
|
||||||
|
{file = "tree_sitter_rust-0.24.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:01a46622735498493f29f3e628a90de95c96a07bfbeb88996243eb986b1cee36"},
|
||||||
|
{file = "tree_sitter_rust-0.24.2-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e033c5a93b57c88e0a835880de39fc802909ff69f57aaff6000211c196ea5190"},
|
||||||
|
{file = "tree_sitter_rust-0.24.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d76d1208c3638b871236090759dfc13d478921320653a6c9da5336e7c58f65a"},
|
||||||
|
{file = "tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87930163a462408c49ab62c667e74029bc26b4cc7123dd1bdc7352215786c64a"},
|
||||||
|
{file = "tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da2b86099028fd42c6cd32878b7b16b01f8aac0f7b0e98742b7fa6bc3cf09b89"},
|
||||||
|
{file = "tree_sitter_rust-0.24.2-cp39-abi3-win_amd64.whl", hash = "sha256:4529c125d928882ddfb879fdc6bc0704913261ecc078b6fa7902559e0daf200d"},
|
||||||
|
{file = "tree_sitter_rust-0.24.2-cp39-abi3-win_arm64.whl", hash = "sha256:66ba90f61bd54f4c4f5d30434957daf64507c16b0313df76becb37d63f70a227"},
|
||||||
|
{file = "tree_sitter_rust-0.24.2.tar.gz", hash = "sha256:54fb02a5911e345308b405174465112479f56dc39e3f1e7744d7568595f00db9"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-scala"
|
||||||
|
version = "0.26.2"
|
||||||
|
description = "Scala grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_scala-0.26.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:beab6a0397d4f45cecaed715f224f444652d6bb1c5318d46c641893cd91de98d"},
|
||||||
|
{file = "tree_sitter_scala-0.26.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:54d2e92824c7908b7e688f218dc1d65c5684c365cd7a3e8028e8860847aeabd4"},
|
||||||
|
{file = "tree_sitter_scala-0.26.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:679bb420771e236d6bcc60c2c7f6096f344cf07d829a7f401250735ad4f4a695"},
|
||||||
|
{file = "tree_sitter_scala-0.26.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e68e55c50fa7e7f2866ce7ca590df8df6407dd7a85e484e9f20e974cbc6d8f2"},
|
||||||
|
{file = "tree_sitter_scala-0.26.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a2ba6e45827e01f11790b24cdae357db51ede59ee7ee5c8b7eccc61e9a7782ea"},
|
||||||
|
{file = "tree_sitter_scala-0.26.2-cp39-abi3-win_amd64.whl", hash = "sha256:eba630fc41cee1093005883fa70e275ecb26351e3ca6471ac2f6f83779478d07"},
|
||||||
|
{file = "tree_sitter_scala-0.26.2-cp39-abi3-win_arm64.whl", hash = "sha256:0ac121c9afd813e0820c9f0e90c2d028ca90e096298cbcab2f139ba5f4a8aaaa"},
|
||||||
|
{file = "tree_sitter_scala-0.26.2.tar.gz", hash = "sha256:2f6c38288c08f8c69bcbd87f4c6904287651e3d2ff4d7730d7154177e9115fda"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-swift"
|
||||||
|
version = "0.7.3"
|
||||||
|
description = "Swift grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_swift-0.7.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2531ec866c22ea52384e2786e07f3b2bb396c6446428a2df02cc74af3f7e6b6a"},
|
||||||
|
{file = "tree_sitter_swift-0.7.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ee627e027d0868c552beca13dcdfa9944662b126f642464c5038ee3204e68340"},
|
||||||
|
{file = "tree_sitter_swift-0.7.3-cp38-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f38feeb4f7350c8b30d567a0dc08bf1eeaa67c241b6888d72a45a8b1a4aa7187"},
|
||||||
|
{file = "tree_sitter_swift-0.7.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eee02fecb60a07267edd123148c583d6ec9efc5d7fcb25e53da4e56869fd4cf3"},
|
||||||
|
{file = "tree_sitter_swift-0.7.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f30c30831f090ebe245f54ddcd280d2c5f7020ba17d6bbec1662bbfae140c467"},
|
||||||
|
{file = "tree_sitter_swift-0.7.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01c1e812289a2f7f01f63627a5d94a0b57d69332e8b52624becfe79ee8061651"},
|
||||||
|
{file = "tree_sitter_swift-0.7.3-cp38-abi3-win_amd64.whl", hash = "sha256:4b1de6122cbd82b2cea6d3a295f9f5f9297601b829061119e161da17a7ba7d17"},
|
||||||
|
{file = "tree_sitter_swift-0.7.3-cp38-abi3-win_arm64.whl", hash = "sha256:af44acc50d16f284abb607ae0cf7f81011d5566283d6c62a045a549a9331a653"},
|
||||||
|
{file = "tree_sitter_swift-0.7.3.tar.gz", hash = "sha256:a87f1dba3050a346ee3442aad8d727afd74555dea258e31c71c7934d8c04af9b"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.23,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-typescript"
|
||||||
|
version = "0.23.2"
|
||||||
|
description = "TypeScript and TSX grammars for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478"},
|
||||||
|
{file = "tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8"},
|
||||||
|
{file = "tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b1eed5b0b3a8134e86126b00b743d667ec27c63fc9de1b7bb23168803879e31"},
|
||||||
|
{file = "tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e96d36b85bcacdeb8ff5c2618d75593ef12ebaf1b4eace3477e2bdb2abb1752c"},
|
||||||
|
{file = "tree_sitter_typescript-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d4f0f9bcb61ad7b7509d49a1565ff2cc363863644a234e1e0fe10960e55aea0"},
|
||||||
|
{file = "tree_sitter_typescript-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:3f730b66396bc3e11811e4465c41ee45d9e9edd6de355a58bbbc49fa770da8f9"},
|
||||||
|
{file = "tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7"},
|
||||||
|
{file = "tree_sitter_typescript-0.23.2.tar.gz", hash = "sha256:7b167b5827c882261cb7a50dfa0fb567975f9b315e87ed87ad0a0a3aedb3834d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.23,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-verilog"
|
||||||
|
version = "1.0.3"
|
||||||
|
description = "Verilog grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_verilog-1.0.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ee20fe0e21c93bf1a10e20c13cbca959eb3c9693194afb90b0567758cbf1744e"},
|
||||||
|
{file = "tree_sitter_verilog-1.0.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5b9d70d86cf6913abc08766b6180e285d72848c7491a3f3f8e7bb8d8c440049d"},
|
||||||
|
{file = "tree_sitter_verilog-1.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d617dff782a8bf56fabac8d1e782ee4ca9ebe2977682eb02d1596ff7ef89958"},
|
||||||
|
{file = "tree_sitter_verilog-1.0.3-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:747dd7d4bc95fb389bc37225f82d16f0c40549856e9a244be3ff9d7bfe62b730"},
|
||||||
|
{file = "tree_sitter_verilog-1.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0476d1f828954683aba38d48a7089e8b698767269950afc7615527a45de641e5"},
|
||||||
|
{file = "tree_sitter_verilog-1.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:da82da153a8d515941da26d84d51b6b79d0fe42d0a0de19845562c3b1dd091c1"},
|
||||||
|
{file = "tree_sitter_verilog-1.0.3-cp39-abi3-win_arm64.whl", hash = "sha256:11576eaa43f89266ab8869fb8d2fb1c22c8da74aa8dc82e67259d6560635c68f"},
|
||||||
|
{file = "tree_sitter_verilog-1.0.3.tar.gz", hash = "sha256:d4043cba50e1ba8402396e3106e17de755c86eca311b23ab826e018ea9818984"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-zig"
|
||||||
|
version = "1.1.2"
|
||||||
|
description = "Zig grammar for tree-sitter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["claude"]
|
||||||
|
files = [
|
||||||
|
{file = "tree_sitter_zig-1.1.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e7542354a5edba377b5692b2add4f346501306d455e192974b7e76bf1a61a282"},
|
||||||
|
{file = "tree_sitter_zig-1.1.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:daa2cdd7c1a2d278f2a917c85993adb6e84d37778bfc350ee9e342872e7f8be2"},
|
||||||
|
{file = "tree_sitter_zig-1.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1962e95067ac5ee784daddd573f828ef32f15e9c871967df6833d3d389113eae"},
|
||||||
|
{file = "tree_sitter_zig-1.1.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e924509dcac5a6054da357e3d6bcf37ea82984ee1d2a376569753d32f61ea8bb"},
|
||||||
|
{file = "tree_sitter_zig-1.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d8f463c370cdd71025b8d40f90e21e8fc25c7394eb64ebd53b1e566d712a3a68"},
|
||||||
|
{file = "tree_sitter_zig-1.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:7b94f00a0e69231ac4ebf0aa763734b9b5637e0ff13634ebfe6d13fadece71e9"},
|
||||||
|
{file = "tree_sitter_zig-1.1.2-cp39-abi3-win_arm64.whl", hash = "sha256:88152ebeaeca1431a6fc943a8b391fee6f6a8058f17435015135157735061ddf"},
|
||||||
|
{file = "tree_sitter_zig-1.1.2.tar.gz", hash = "sha256:da24db16df92f7fcfa34448e06a14b637b1ff985f7ce2ee19183c489e187a92e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["tree-sitter (>=0.22,<1.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.16.0"
|
version = "4.16.0"
|
||||||
@@ -2573,4 +3445,4 @@ propcache = ">=0.2.1"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.13,<4.0"
|
python-versions = ">=3.13,<4.0"
|
||||||
content-hash = "2ac8d07b41e04495f16ac9da7093f96ec08d094402ebafe48a421a40a98c7d05"
|
content-hash = "fffbbc108bcb642d179b4d838b676bd848ab2f4af8d56314d056c74248fe5d06"
|
||||||
|
|||||||
+4
-2
@@ -21,8 +21,7 @@ dependencies = [
|
|||||||
"asyncpg (>=0.31.0,<0.32.0)",
|
"asyncpg (>=0.31.0,<0.32.0)",
|
||||||
"psycopg2-binary (>=2.9.12,<3.0.0)",
|
"psycopg2-binary (>=2.9.12,<3.0.0)",
|
||||||
"redis (>=8.1.0,<9.0.0)",
|
"redis (>=8.1.0,<9.0.0)",
|
||||||
"aio-pika (>=10.0.1,<11.0.0)",
|
"aio-pika (>=10.0.1,<11.0.0)"
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.group.web.dependencies]
|
[tool.poetry.group.web.dependencies]
|
||||||
@@ -45,6 +44,9 @@ requests-async = ">=0.2.4,<0.3.0"
|
|||||||
aiosmtpd = ">=1.4.6,<2.0.0"
|
aiosmtpd = ">=1.4.6,<2.0.0"
|
||||||
jinja2 = ">=3.1.6,<4.0.0"
|
jinja2 = ">=3.1.6,<4.0.0"
|
||||||
|
|
||||||
|
[tool.poetry.group.claude.dependencies]
|
||||||
|
graphifyy = ">=0.9.63,<0.10.0"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|||||||
@@ -10,30 +10,84 @@ A FastAPI project combining Excel and digital data ("The Great Excel project tha
|
|||||||
- PostgreSQL (asyncpg, psycopg2)
|
- PostgreSQL (asyncpg, psycopg2)
|
||||||
- Pydantic 2 / Pydantic Settings
|
- Pydantic 2 / Pydantic Settings
|
||||||
- Poetry — dependency management
|
- Poetry — dependency management
|
||||||
|
- Redis — caching, rate limiting, token revocation
|
||||||
|
- RabbitMQ (aio-pika) — background email workers
|
||||||
|
- Jinja2 — HTML email templates
|
||||||
|
- Frontend: React 19, TypeScript, Vite, react-router-dom, CSS Modules (yarn)
|
||||||
- Docker, Ansible — deployment
|
- Docker, Ansible — deployment
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
|
├── cache/ # Redis client, rate limiting
|
||||||
|
├── daemons/ # background worker entrypoints (BaseDaemon, registry)
|
||||||
├── database/ # DB CRUD operations
|
├── database/ # DB CRUD operations
|
||||||
├── errors/ # HTTP errors
|
├── errors/ # HTTP errors
|
||||||
├── logging/ # logging middleware
|
├── logging/ # queue-based logging infra + HTTP middleware
|
||||||
|
├── messaging/ # RabbitMQ client, producers, consumers, topology
|
||||||
├── migrations/ # Alembic migrations
|
├── migrations/ # Alembic migrations
|
||||||
├── models/ # Pydantic and SQLAlchemy models, configs
|
├── models/ # Pydantic and SQLAlchemy models, configs, RabbitMQ topology
|
||||||
├── reports/ # reports
|
├── reports/ # reports
|
||||||
├── service/ # business logic (auth, users_crud)
|
├── service/ # business logic (auth, users_crud, email sending)
|
||||||
└── web/ # routes (protected_routes)
|
└── web/ # routes (protected_routes)
|
||||||
|
|
||||||
|
frontend/ # React app (login / password-reset pages), see "Frontend"
|
||||||
```
|
```
|
||||||
|
|
||||||
Layers are connected top to bottom: `web → service → database → models`.
|
Layers are connected top to bottom: `web → service → database → models`.
|
||||||
|
|
||||||
|
## Background workers (RabbitMQ)
|
||||||
|
|
||||||
|
Email sending (welcome / password-reset) runs as separate daemon processes,
|
||||||
|
decoupled from the web API via a RabbitMQ topic exchange:
|
||||||
|
|
||||||
|
```
|
||||||
|
main.py / daemon_run.py → apply_topology() → RabbitMQ ("email" exchange)
|
||||||
|
├── queue_welcome_email → WelcomeEmailConsumer
|
||||||
|
└── queue_reset_email → ResetEmailConsumer
|
||||||
|
```
|
||||||
|
|
||||||
|
Each queue has a matching dead-letter queue for messages that fail
|
||||||
|
permanently (bad data, non-retryable errors) instead of retrying forever.
|
||||||
|
Run a worker locally with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python daemon_run.py welcome_email # single daemon
|
||||||
|
python daemon_run.py --all # all daemons enabled in configs/daemons.json
|
||||||
|
```
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
- JWT access + refresh tokens
|
- JWT access + refresh tokens
|
||||||
- Refresh token is stored in the DB as a SHA256 hash, with rotation and revocation support
|
- Refresh token is stored in the DB as a SHA256 hash, with rotation and revocation support
|
||||||
- Passwords are hashed with bcrypt
|
- Passwords are hashed with bcrypt
|
||||||
- RBAC: direct user permissions + permissions via groups, checked through `require_permissions()`
|
- RBAC: direct user permissions + permissions via groups, checked through `require_permissions()`
|
||||||
|
- Login: `POST /protected/token` (OAuth2 form-data: `username` = email, `password`); returns the access token and sets the refresh token as an httpOnly cookie. Failed logins are rate-limited (5 per minute per IP)
|
||||||
|
- Password reset: `POST /protected/reset?email=...` generates a temporary password, saves it and queues an email with it (3 requests per 10 minutes per IP). The response is the same whether or not the email exists
|
||||||
|
- CORS: the allowed frontend origin is taken from the `FRONT_URL` variable in `configs/.env` (required, e.g. `http://localhost:5173`)
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
React + Vite app in `frontend/` (login and password-reset pages).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
yarn install
|
||||||
|
yarn dev # http://localhost:5173
|
||||||
|
```
|
||||||
|
|
||||||
|
Environment variables for the frontend live in `frontend/configs/.env`
|
||||||
|
(Vite is configured to read them from there). Only variables prefixed with
|
||||||
|
`VITE_` are exposed to the browser code:
|
||||||
|
|
||||||
|
```text
|
||||||
|
VITE_BACKEND_URL=http://localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
The frontend calls the backend directly (no dev proxy), so `FRONT_URL` in the
|
||||||
|
backend's `configs/.env` must match the address the frontend runs on.
|
||||||
|
Restart `yarn dev` after changing either file.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
@@ -46,12 +100,72 @@ tests/
|
|||||||
|
|
||||||
Uses pytest, pytest-asyncio, pytest-cov, pytest-mock, allure-pytest.
|
Uses pytest, pytest-asyncio, pytest-cov, pytest-mock, allure-pytest.
|
||||||
|
|
||||||
|
### Allure report
|
||||||
|
|
||||||
|
`make allure` needs the Allure **command-line tool** (Java-based, not a pip
|
||||||
|
package) — `allure-pytest` only writes raw result files, the CLI turns them
|
||||||
|
into an HTML report.
|
||||||
|
|
||||||
|
1. Download Allure **2.44.0** from
|
||||||
|
[github.com/allure-framework/allure2/releases](https://github.com/allure-framework/allure2/releases).
|
||||||
|
2. Extract it into `.venv/allure-2.44.0/` so that `.venv/allure-2.44.0/bin/allure`
|
||||||
|
exists — this matches the `ALLURE` variable already set in `makefile`.
|
||||||
|
3. If you install it somewhere else (or on Windows), update the `ALLURE`
|
||||||
|
variable at the top of `makefile` to point to your actual `allure`
|
||||||
|
binary path — the Windows path is already there, commented out.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test # runs pytest, writes results to tests/allure-results/reports
|
||||||
|
make allure # builds tests/allure-results/html/index.html from those results
|
||||||
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
Requires Poetry itself to be installed first (it's a global tool, not a
|
||||||
|
project dependency). Recommended via [pipx](https://pipx.pypa.io/):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipx install poetry
|
||||||
|
```
|
||||||
|
|
||||||
|
or via the official installer:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
```
|
||||||
|
|
||||||
|
Then install the project dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
poetry install
|
poetry install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Codebase knowledge graph (graphify)
|
||||||
|
|
||||||
|
The repo can be explored as a navigable knowledge graph via the
|
||||||
|
[graphify](https://github.com/safishamsi/graphify) Claude Code skill —
|
||||||
|
useful for onboarding or tracing how a change ripples across modules.
|
||||||
|
It lives in its own Poetry group (`claude`) so it's never installed in
|
||||||
|
`web`/`daemon`/prod images:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
poetry install --with claude
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, inside a Claude Code session in this repo, run:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/graphify
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds `graphify-out/graph.html` (open directly in a browser),
|
||||||
|
`graphify-out/GRAPH_REPORT.md` (god nodes, surprising cross-module
|
||||||
|
connections, suggested questions), and `graphify-out/graph.json` (raw
|
||||||
|
graph data). Ask follow-up questions about the codebase directly — once
|
||||||
|
`graphify-out/graph.json` exists, Claude answers from the graph instead
|
||||||
|
of rebuilding it. `graphify-out/` is gitignored: it's regenerable local
|
||||||
|
output, not part of the codebase.
|
||||||
|
|
||||||
## Running migrations
|
## Running migrations
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Vendored
+7
-7
@@ -6,19 +6,19 @@ class RateLimit:
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.errors=Errors()
|
self.errors=Errors()
|
||||||
|
|
||||||
async def rate_limit(self, ip:str)->None:
|
async def rate_limit(self, ip:str, action:str, limit:int=5, window:int=60)->None:
|
||||||
|
|
||||||
key=f"action attempt {ip}"
|
key=f"rate_limit:{action}:{ip}"
|
||||||
attempts = await redis_client.incrby(key)
|
attempts = await redis_client.incrby(key)
|
||||||
|
|
||||||
if attempts == 1:
|
if attempts == 1:
|
||||||
await redis_client.expire(key, 60)
|
await redis_client.expire(key, window, nx=True)
|
||||||
|
|
||||||
if attempts>5:
|
if attempts>limit:
|
||||||
raise self.errors.rate_limit_error(detail="too many attempts", retry_after=60)
|
raise self.errors.rate_limit_error(detail="too many attempts", retry_after=window)
|
||||||
|
|
||||||
async def check_rate_limit(self, client_ip:str) -> None:
|
async def check_rate_limit(self, client_ip:str, action:str) -> None:
|
||||||
await self.rate_limit(client_ip)
|
await self.rate_limit(client_ip, action)
|
||||||
|
|
||||||
|
|
||||||
rate_limiter=RateLimit()
|
rate_limiter=RateLimit()
|
||||||
@@ -36,20 +36,23 @@ class JwtCrudActions:
|
|||||||
|
|
||||||
async def create_and_update_token(self, data: dict, old_jti: UUID, new_jti: UUID) -> bool:
|
async def create_and_update_token(self, data: dict, old_jti: UUID, new_jti: UUID) -> bool:
|
||||||
async with self.Session() as session, session.begin():
|
async with self.Session() as session, session.begin():
|
||||||
new_token = RefreshTokens(**data)
|
revoke_query = (
|
||||||
|
|
||||||
query = (
|
|
||||||
update(RefreshTokens)
|
update(RefreshTokens)
|
||||||
.where(RefreshTokens.id == old_jti, RefreshTokens.is_revoked.is_(False))
|
.where(RefreshTokens.id == old_jti, RefreshTokens.is_revoked.is_(False))
|
||||||
.values(is_revoked=True, replaced_by=new_jti)
|
.values(is_revoked=True)
|
||||||
.returning(RefreshTokens.id)
|
.returning(RefreshTokens.id)
|
||||||
)
|
)
|
||||||
updated_id = (await session.execute(query)).scalar_one_or_none()
|
updated_id = (await session.execute(revoke_query)).scalar_one_or_none()
|
||||||
|
|
||||||
if updated_id is None:
|
if updated_id is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
session.add(new_token)
|
session.add(RefreshTokens(**data))
|
||||||
|
await session.flush()
|
||||||
|
|
||||||
|
await session.execute(
|
||||||
|
update(RefreshTokens).where(RefreshTokens.id == old_jti).values(replaced_by=new_jti)
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,10 @@ class ResetEmailConsumer:
|
|||||||
message_id_ctx.set(str(uuid4()))
|
message_id_ctx.set(str(uuid4()))
|
||||||
async with message.process(ignore_processed=True):
|
async with message.process(ignore_processed=True):
|
||||||
data = json.loads(message.body)
|
data = json.loads(message.body)
|
||||||
daemon_logger.info(f"Обрабатываю: {data}, метка: {message.routing_key}")
|
daemon_logger.info(f"Обрабатываю reset для {data.get('email')}:, метка: {message.routing_key}")
|
||||||
try:
|
try:
|
||||||
await self.daemon.send_email(data.get("email"), data.get("temp_password"))
|
await self.daemon.send_email(data.get("email"), data.get("temp_password"))
|
||||||
|
|
||||||
except (
|
except (
|
||||||
smtplib.SMTPServerDisconnected,
|
smtplib.SMTPServerDisconnected,
|
||||||
smtplib.SMTPConnectError,
|
smtplib.SMTPConnectError,
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ class Env(Base):
|
|||||||
EMAIL_LOGIN:str
|
EMAIL_LOGIN:str
|
||||||
EMAIL_PASSWORD:str
|
EMAIL_PASSWORD:str
|
||||||
|
|
||||||
|
FRONT_HOST:str
|
||||||
|
FRONT_PORT:int
|
||||||
|
|
||||||
|
VITE_BACKEND_HOST:str
|
||||||
|
VITE_BACKEND_PORT:str
|
||||||
|
|
||||||
PROD_MODE:bool
|
PROD_MODE:bool
|
||||||
|
|
||||||
model_config=SettingsConfigDict(env_file="configs/.env", extra=None)
|
model_config=SettingsConfigDict(env_file="configs/.env", extra=None)
|
||||||
|
|||||||
@@ -205,8 +205,6 @@ class CurrentUserService:
|
|||||||
else:
|
else:
|
||||||
raise self.error.not_found_error(detail="Refresh Token Not Found")
|
raise self.error.not_found_error(detail="Refresh Token Not Found")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def login(self, form_data_email:str, form_data_password:str, request:Request)->tuple[str, str]:
|
async def login(self, form_data_email:str, form_data_password:str, request:Request)->tuple[str, str]:
|
||||||
'''revoke all the old refresh tokens'''
|
'''revoke all the old refresh tokens'''
|
||||||
user = await self._check(form_data_email, form_data_password)
|
user = await self._check(form_data_email, form_data_password)
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
|
import secrets
|
||||||
|
import string
|
||||||
from datetime import UTC, datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
@@ -22,6 +24,16 @@ class HashService:
|
|||||||
|
|
||||||
def token_to_hash(self, token:str)->str:
|
def token_to_hash(self, token:str)->str:
|
||||||
return hashlib.sha256(token.encode("utf-8")).hexdigest()
|
return hashlib.sha256(token.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
async def generate_temp_password(self, length:int=16)->str:
|
||||||
|
SPECIAL = "$@#%!^&*()-_+=[]{}"
|
||||||
|
groups = [string.ascii_uppercase, string.ascii_lowercase, string.digits, SPECIAL]
|
||||||
|
chars = [secrets.choice(g) for g in groups] # по одному из каждой группы
|
||||||
|
pool = "".join(groups)
|
||||||
|
chars += [secrets.choice(pool) for _ in range(length - len(chars))]
|
||||||
|
secrets.SystemRandom().shuffle(chars) # чтобы спецсимвол не всегда стоял первым
|
||||||
|
return "".join(chars)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'''jwt'''
|
'''jwt'''
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
from src.database.auth.refresh_tokens import JwtCrudActions
|
||||||
from src.database.users.crud import UsersCrudActions
|
from src.database.users.crud import UsersCrudActions
|
||||||
from src.errors.http_errors.errors import Errors
|
from src.errors.http_errors.errors import Errors
|
||||||
from src.models.pydantic_models.model import UserCreate, UserOut, UserUpdate
|
from src.models.pydantic_models.model import UserCreate, UserOut, UserUpdate
|
||||||
@@ -12,6 +13,7 @@ class CrudService:
|
|||||||
self.errors=Errors()
|
self.errors=Errors()
|
||||||
self.crud_db_actions=UsersCrudActions()
|
self.crud_db_actions=UsersCrudActions()
|
||||||
self.hash_service=HashService()
|
self.hash_service=HashService()
|
||||||
|
self.jwt_db_actions=JwtCrudActions()
|
||||||
|
|
||||||
|
|
||||||
async def _plain_to_hash(self, user_data:dict)->dict:
|
async def _plain_to_hash(self, user_data:dict)->dict:
|
||||||
@@ -75,6 +77,14 @@ class CrudService:
|
|||||||
if not user_entity:
|
if not user_entity:
|
||||||
raise self.errors.not_found_error(detail="User not found")
|
raise self.errors.not_found_error(detail="User not found")
|
||||||
return UserOut.model_validate(user_entity)
|
return UserOut.model_validate(user_entity)
|
||||||
|
|
||||||
|
async def reset_password(self, email:str)->str:
|
||||||
|
new_password=await self.hash_service.generate_temp_password()
|
||||||
|
|
||||||
|
|
||||||
|
await self.update_user(email, UserUpdate(plain_password=new_password)) # type: ignore[call-arg]
|
||||||
|
|
||||||
|
return new_password
|
||||||
|
|
||||||
async def crud_service()->CrudService:
|
async def crud_service()->CrudService:
|
||||||
return CrudService()
|
return CrudService()
|
||||||
@@ -2,9 +2,11 @@ from fastapi import APIRouter, Cookie, Depends, HTTPException, Request, Response
|
|||||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||||
|
|
||||||
from src.cache.rate_limit import rate_limiter
|
from src.cache.rate_limit import rate_limiter
|
||||||
|
from src.messaging.producers.producers import email_producer
|
||||||
from src.models.configs_read.env import env_settings
|
from src.models.configs_read.env import env_settings
|
||||||
from src.models.pydantic_models.model import UserOut
|
from src.models.pydantic_models.model import UserOut
|
||||||
from src.service.auth.auth import CurrentUserService, auth_service
|
from src.service.auth.auth import CurrentUserService, auth_service
|
||||||
|
from src.service.users_crud.users_crud import CrudService, crud_service
|
||||||
|
|
||||||
router=APIRouter(prefix="/protected")
|
router=APIRouter(prefix="/protected")
|
||||||
oauth2_schema=OAuth2PasswordBearer(tokenUrl="/protected/token", refreshUrl="/protected/refresh")
|
oauth2_schema=OAuth2PasswordBearer(tokenUrl="/protected/token", refreshUrl="/protected/refresh")
|
||||||
@@ -31,7 +33,7 @@ async def get_access_token(request: Request,
|
|||||||
try:
|
try:
|
||||||
access_token, refresh_token=await auth.login(form_data_email=form_data.username, form_data_password=form_data.password, request=request)
|
access_token, refresh_token=await auth.login(form_data_email=form_data.username, form_data_password=form_data.password, request=request)
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
await rate_limiter.rate_limit(client_ip)
|
await rate_limiter.rate_limit(client_ip, "login", 5, 60)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
@@ -74,7 +76,23 @@ async def logout(response:Response,
|
|||||||
response.delete_cookie("refresh_token")
|
response.delete_cookie("refresh_token")
|
||||||
return await auth.logout(refresh_token, access_token)
|
return await auth.logout(refresh_token, access_token)
|
||||||
|
|
||||||
|
@router.post("/reset")
|
||||||
|
async def reset(request: Request,
|
||||||
|
email:str,
|
||||||
|
crud:CrudService=Depends(crud_service)
|
||||||
|
):
|
||||||
|
|
||||||
|
client_ip = request.headers.get('x-forwarded-for', '').split(',')[0].strip() or (request.client.host if request.client else 'unknown')
|
||||||
|
await rate_limiter.rate_limit(client_ip, "reset", 3, 600)
|
||||||
|
try:
|
||||||
|
temp_password=await crud.reset_password(email)
|
||||||
|
await email_producer.send_reset_email(email, temp_password)
|
||||||
|
except HTTPException as exc:
|
||||||
|
if exc.status_code!=404:
|
||||||
|
raise
|
||||||
|
return {"detail":"New password was sent"}
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@router.get("/me")
|
||||||
async def protected(current_user:UserOut=Depends(require_permissions()))->dict:
|
async def protected(current_user:UserOut=Depends(require_permissions()))->UserOut:
|
||||||
return {"protected router": "Hello, this is a protected router"}
|
return current_user
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user