When viewed_user is set, the sidebar should render the full user
navigation (connections, catalog, sync, profile) instead of the
admin-only panel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allow admin (not just system) to delete users directly from the list
without navigating to the detail page. Also enables role management
link and create-user role selector for admin role.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When Evotor sends a new /user/create for an existing userId, the new
password should replace the old one so /user/verify stays in sync.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Evotor sends userId but not necessarily a matching phone/email.
Now tries evotor_user_id first, then falls back to email/phone.
Also removed the requirement for username/phone when userId is present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add middle_name input to the edit form (3-column name grid)
- Save middle_name in the edit handler
- Show role selector for admin role (previously system-only)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously these actions were restricted to system role only. Admin and
system are now treated equally across: API logs view, user role editing,
user deletion, and role/permissions management. Regular users remain blocked.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All POST/action routes in connections.py were using get_current_user,
which returns the real logged-in admin instead of the impersonated user.
Disconnect, test, save and manual token routes now all operate on the
viewed user so admin impersonation works correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a connection row exists for the user but with a different/null
evotor_user_id, the lookup by evotor_user_id alone missed it and tried
to INSERT, hitting the unique constraint on user_id. Now looks up by
either evotor_user_id or user_id, and always syncs both fields on update.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Accept phone as an alternative to username for user lookup
- On first auth when user has no password set, save the provided
password and activate the account (same logic as /user/create)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When Evotor sends a password in the payload, hash and store it
immediately and set the user to active — skipping the invite flow.
Without a password, behaviour is unchanged (pending status + invite email).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
userId is nested inside body.data, and event types are
ApplicationInstalled / ApplicationUninstalled (not install/uninstall).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- 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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
- 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>
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>
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>