Commit Graph

95 Commits

Author SHA1 Message Date
mguschin
dc32bef7e8 fix: read email/phone from top-level body fields in /user/create
Evotor sends user fields (email, phone_number, first_name, last_name)
at the top level of the webhook body, not inside customField. Now we
check both locations with top-level taking precedence. Also store the
full body in evotor_meta instead of just the customField subset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:48:15 +03:00
mguschin
a3f6697bc4 debug: log raw body on /user/install to identify Evotor payload structure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:45:55 +03:00
mguschin
175f1f4c27 feat: add /user/install webhook + exclude admins from third-party API tasks
- Add POST /user/install endpoint handling Evotor app install/uninstall
  events: uninstall suspends the user and marks connection offline;
  reinstall reactivates a suspended account
- Exclude admin and system role users from refresh_catalog,
  refresh_vk_catalog, and mirror_to_vk periodic tasks by joining users
  table and filtering role = 'user'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:41:08 +03:00
mguschin
9f87458e0c fix: exclude admin/system users from all third-party API tasks
Catalog, VK catalog, and VK sync tasks were querying all connections
regardless of user role. Admin and system accounts with stored tokens
were generating unnecessary Evotor and VK API calls. Now all three
tasks join to the users table and filter role = 'user' only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:14:26 +03:00
mguschin
e0594f67a8 feat: add favicon (SVG shopping-bag logo + ICO fallback)
Orange #FF5500 rounded-square with shopping-bag icon matching the
sidebar logo. Includes SVG (modern), ICO (16+32px, legacy), and
apple-touch-icon PNG.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:50:08 +03:00
mguschin
eb4165e48b feat: apply new Мои Товары design system across all templates
Replace Pico CSS with custom design: dark sidebar layout, Golos Text +
JetBrains Mono fonts, orange accent (#FF5500), new component classes
(cards, tables, buttons, tags, toggles, alerts, tabs, login split-panel).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:43:08 +03:00
mguschin
75513e647d revert: restore horizontal scroll table layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:11:54 +03:00
mguschin
db0a3bb4d6 fix: table compresses to page width with fixed layout and ellipsis overflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:09:32 +03:00
mguschin
545c6aade6 fix: table fills full card width
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:06:18 +03:00
mguschin
98264d42af fix: card clips overflowing table content to screen width
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:03:57 +03:00
mguschin
fca3ca115e fix: show create user errors inside dialog, pre-fill form on error
Used novalidate + server-side validation so errors appear in the dialog
rather than as browser-native popups. Form fields retain submitted values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:01:46 +03:00
mguschin
52825f70de feat: admin can create users from /admin/users page
Adds a dialog form on the users list. Validates email uniqueness,
password length. Creates user as active with confirmed email.
System role can assign admin role; admin role can only create users.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 20:58:00 +03:00
mguschin
8549e98f8d feat: hide user nav links for admin, redirect admin to /admin/users on login
Connections/Catalog/VK/Sync nav links only shown for regular users or when
admin is viewing as a user. Admin/system users land on /admin/users after login.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 20:48:02 +03:00
mguschin
5e7be16755 feat: remove register, add evo webhooks, admin view-as user
- Remove /register route and nav links (users created via Evotor webhook)
- Fix evotor_webhooks.py: use phone=None instead of phone="" to avoid unique constraint
- Add admin "view as user" feature: POST /admin/users/{id}/view-as sets viewed_user_id
  in session; POST /admin/view-as/stop clears it
- catalog, vk_catalog, sync, connections GET routes use get_viewed_user() so admins
  see another user's data while browsing
- Orange banner shown at top when admin is viewing as another user

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 20:44:25 +03:00
mguschin
1729ff9b7b fix: disabling last store/group no longer resets all to enabled
Added store_filters_seeded / group_filters_seeded flags to SyncConfig.
_enabled_*_ids now returns None (all enabled) only before first toggle,
not when the filter table is empty due to all being disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:36:14 +03:00
mguschin
ebcca2a699 fix: make users.phone nullable to allow admin creation without phone
Phone is optional — admin users created via script don't have one.
Added migration 0010 to alter the column, updated create_admin.py to
pass None instead of empty string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:17:50 +03:00
mguschin
7860256c37 fix: use NULL for empty phone in create_admin to avoid unique index conflict
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:15:40 +03:00
mguschin
ddc3dc0a97 refactor: nginx.conf is source of truth, drop generate script and template
nginx/nginx.conf is symlinked directly into system nginx config.
No need for a per-domain generate script or template — edit the file,
run nginx -t && systemctl reload nginx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:08:11 +03:00
mguschin
ff32812b61 refactor: remove IDN auto-conversion, pass punycode directly to TLS scripts
Simpler than auto-converting: just pass xn----8sbfwtmcso8g.xn--p1ai directly.
Updated usage comments in both scripts to reflect this.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:07:29 +03:00
mguschin
dbb1f48da7 fix: correct punycode for мои-товары.рф and add IDN support to generate-nginx-conf.sh
xn--e1afmapc4af.xn--p1af was wrong; correct punycode is xn----8sbfwtmcso8g.xn--p1ai.
generate-nginx-conf.sh now converts IDN domains to punycode before expanding the
template, so cert paths and server_name directives are always ASCII-safe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:06:54 +03:00
mguschin
23e175d9a8 fix: convert IDN/Cyrillic domains to punycode before calling certbot
certbot rejects non-ASCII domain names; convert using Python's idna
encoder per-label so мои-товары.рф becomes xn--e1afmapc4af.xn--p1af.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:00:05 +03:00
mguschin
e816672e16 perf: reduce RAM usage for 1GB host
- MariaDB: limit innodb buffer pool to 128M, max 20 connections
- Celery worker: concurrency 2→1
- Flower: moved to 'flower' profile (opt-in, not started by default)
  Start with: docker compose --profile flower up -d flower

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 11:16:58 +03:00
mguschin
7df5da76d7 feat: multi-domain nginx configs and TLS scripts for мои-товары.рф / my-products.ru
- nginx/nginx.conf: pre-generated config for both domains (IDN punycode for .рф)
- scripts/generate-nginx-conf.sh: generates sites-available config from template per domain
- scripts/init-letsencrypt.sh: accepts domain as arg (falls back to .env)
- README.md: updated deploy section, removed stale VK_WEIGHT_PRICE_MULTIPLIER, added sync/logs routes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:39:02 +03:00
mguschin
75b3872170 feat: per-task on/off switches on /sync page for staged rollout
Adds evo_mirror_enabled and vk_mirror_enabled flags to SyncConfig.
Each of the three background tasks (Зеркало Эвотор / Зеркало ВК /
Синхронизация) can now be enabled independently from the /sync page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:32:02 +03:00
mguschin
5c2b501749 fix: delete VK product immediately when allow_to_sell becomes false
Previously only the create path checked allow_to_sell. The update path
kept syncing disabled products indefinitely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:20:18 +03:00
mguschin
72194131c7 fix: include price multiplier quantity in VK description postfix
"цена за 10 г." instead of "цена за г." when multiplier is set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:13:39 +03:00
mguschin
02abddc587 refactor: derive VK description postfix from measure_name, drop global postfix setting
Each product's description is now built as "Name (цена за M.)" using its
own measure_name. The global description_postfix setting is removed —
it couldn't handle per-product units.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:08:45 +03:00
mguschin
e169a91146 feat: /sync settings page with price multiplier and description postfix
Adds price_multiplier and description_postfix to SyncConfig. The sync
page at /sync lets users configure them. vk_sync reads these per-user
settings and applies the multiplier to price and appends the postfix
as "(postfix)" to the VK product description.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 23:04:23 +03:00
mguschin
fb3b6e2327 fix: use product name as VK description fallback (VK requires ≥10 chars)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:57:21 +03:00
mguschin
8d97f75fa1 fix: sync Evotor description to VK as-is, remove generated wrapper text
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:53:06 +03:00
mguschin
e0e43f3fc3 fix: VK Market write API expects rubles, read amount field is kopecks
market.add/edit accept price in rubles; market.get returns price.amount
in kopecks. Cache stores rubles (amount/100), send rubles on write.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:49:13 +03:00
mguschin
3ad383d00b fix: remove weight price multiplier, send Evotor prices as-is to VK
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:45:22 +03:00
mguschin
d25caa2b96 fix: VK API returns prices in rubles, not kopecks — remove /100 division
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:40:52 +03:00
mguschin
b926ca0b90 fix: send prices in rubles to VK Market API, not kopecks
VK Market API expects the price field in rubles (float), not kopecks.
Removing the *100 conversion that was inflating all prices by 100x.
Comparison with cached VK prices now also uses rubles consistently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:37:27 +03:00
mguschin
9960d760a0 feat: API request/response logging with admin log viewer
- Add api_logs table (migration 0007) and ApiLog model
- Add web/lib/api_logger.py — httpx wrapper that records every outbound call
- Wire api_logger into vk_sync, vk_catalog, and connections test endpoints
- Add /admin/logs page with filters (service, method, status, time range, URL search) and expandable request/response detail
- Add "Логи" nav link for admin users

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:00:14 +03:00
mguschin
cad0b10fbb fix: delete stale cached albums that no longer exist in VK
After refresh_vk_catalog syncs the album list from VK, remove any
VkCachedAlbum rows whose album_id was not returned by the API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:47:43 +03:00
mguschin
bb9fc71ed8 fix: use item_id (singular) in market.removeFromAlbum call
VK API v5.199 requires item_id not item_ids for market.removeFromAlbum.
The wrong parameter name caused silent failures — products were not
actually removed from their old album when moved to a new one.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:34:14 +03:00
mguschin
83edac4200 fix: read parent_id field from Evotor API and move VK products between albums on group change
- catalog.py: include parent_id as fallback for group_evotor_id (Evotor API returns parent_id instead of group/parentUuid)
- vk_sync.py: on product update, detect album change and call market.removeFromAlbum + market.addToAlbum to move product to the correct album

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:30:01 +03:00
mguschin
7b4f52b005 feat: VK OAuth flow, catalog sync improvements, and expanded test suite
- Add VK OAuth implicit flow: /vk-auth redirect, /vk-callback JS page,
  /vk-callback/save endpoint with state validation
- Add VK_CLIENT_ID/VK_CLIENT_SECRET to config
- Add refresh_token/token_expires_at columns to vk_connections (migration 0006)
- Fix vk_catalog task: handle price/thumb_photo as string or dict (VK API v5.199)
- Fix connections/vk/test: use groups.getById instead of market.getAlbums
  (works with both user and group tokens)
- Add orphan deletion to mirror_to_vk: VK products not in Evotor are removed
- Handle ungrouped Evotor products: push to "Без категории" VK album
- Respect SyncConfig.is_enabled in mirror_to_vk
- Add product count column to catalog groups page
- Add group name column to catalog products page
- Expand test suite: 73 new tests covering connections routes, catalog routes,
  vk_sync task logic, and catalog task helpers (138 total, all passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:09:47 +03:00
mguschin
4f4081c54c config: make domain configurable via DOMAIN env var
Replace hardcoded evosync.ru with a DOMAIN variable read from .env.
nginx.conf is now generated from nginx.conf.template via envsubst;
init-letsencrypt.sh reads DOMAIN from .env and fails loudly if unset.
README documents the new variable and first-deploy TLS workflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:01:38 +03:00
mguschin
796cf49ff9 feat: Evotor + VK catalog sync, connections, and store/group filters
- Evotor catalog: background Celery task syncing stores/groups/products
  from Evotor API; UI pages with per-store and per-group sync toggles
- VK connection: manual token + group ID entry with inline test button
- Evotor connection: inline test button (calls /stores)
- VK catalog: background task syncing VK Market albums and products;
  separate catalog UI at /vk-catalog/albums
- SyncFilter extended to support entity_type=group with parent_entity_id
- Migration 0004: vk_cached_albums + vk_cached_products tables
- Beat schedule updated to run both refresh_catalog and refresh_vk_catalog
- README updated with new schema, routes, tasks, and config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 18:09:11 +03:00
mguschin
7a06045bef docs: rewrite README in English with full architecture reference
Also add itsdangerous to requirements.txt (missing implicit dep of
starlette SessionMiddleware) and a create_admin.py script for
bootstrapping a system-role user with all permissions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:38:14 +03:00
mguschin
fc65e591b3 test: add test suite with 65 tests, 73% coverage
- Unit tests: password hashing, notification providers, webhook field parsing
- Integration tests: auth routes (register/login/confirm-email/logout),
  invite flow, Evotor webhooks (/user/create, /user/verify, /user/token),
  admin panel (access control, activate/suspend/delete/reset-password)
- conftest: SQLite in-memory engine, transactional sessions, factory-boy
  factories (UserFactory with UserRoleEnum variants)
- Fix bcrypt: replace passlib (broken on Python 3.14 + bcrypt 5.x) with
  direct bcrypt calls; drop passlib from requirements.txt
- Fix datetime.utcnow() deprecation across routes and tests
- Fix Jinja2 TemplateResponse signature (request as first positional arg)
- Add coverage config to pyproject.toml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 12:27:42 +03:00
mguschin
5ead89e0cf feat: Evotor user lifecycle, RBAC, admin panel
- Receive Evotor webhooks: POST /user/create, /user/verify, /user/token
- Create users in pending status; match to existing users by email/phone
- Send invite link via Celery notification task; user sets password at /invite
- Abstract EmailProvider/SMSProvider with ConsoleEmailProvider default
- Role-based access control: role enum on users + roles/permissions tables
- Admin panel: /admin/users (list, filter, search, paginate), user detail card
  with activate/suspend/reset-password/send-invite/edit/delete actions
- Admin roles management: /admin/roles with per-role permission assignment
- Extend user profile card: role, status, Evotor ID, email confirmation badge
- Auth routes: register, login, logout, confirm-email, forgot/reset password
- Alembic migrations 0002 (full schema + new fields) and 0003 (RBAC + seeds)
- Port Pico CSS + Bootstrap Icons UI from Node.js commit (854c912)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 12:01:36 +03:00
ba34adbbcf docs: add implementation plan for Evotor user lifecycle + RBAC + admin panel 2026-04-28 11:46:49 +03:00
mguschin
2df4898098 Ignore web-resources directory
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 23:08:08 +03:00
mguschin
15a362ca42 Add EvoSync v3 environment scaffold
FastAPI + Celery + Redis + MariaDB stack with 6-service docker-compose.
Includes project skeleton (config, database, models, tasks, migrations)
and health endpoint with passing test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 23:04:50 +03:00
mguschin
049e82654d revert v2. 2026-03-27 15:42:52 +03:00
mguschin
854c912a88 Migrate web app from Python/FastAPI to Node.js/TypeScript
Replace the entire Python/FastAPI backend with a Node.js/TypeScript stack:
- Framework: Hono + @hono/node-server
- Templates: Nunjucks (.njk) replacing Jinja2 (.html)
- ORM: Drizzle ORM with mysql2 (same MariaDB schema, no migrations needed)
- Sessions: hono-sessions with CookieStore
- CSS: Pico CSS v2 replacing Bootstrap 5 (Bootstrap Icons CDN kept)
- Dev: tsx watch; Prod: tsc + node dist/index.js

Original Python app preserved in web-python/ as backup.
Updated Dockerfile.web and docker-compose.yml for Node.js deployment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
v2.0.0
2026-03-17 19:33:32 +03:00
mguschin
db0c1cbed3 Release version 1.9.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v1.9.0
2026-03-10 17:21:24 +03:00