ASP.NET Core's [Authorize(Roles=...)] relies on ClaimsIdentity.RoleClaimType to
match, which may not be wired to "role" in the OpenIddict validation handler's
identity (depending on middleware order with AddIdentity). Tokens clearly carry
"role": "Admin" but IsInRole("Admin") returns false.
- Register AddAuthorization policy "AdminAccess" that checks the `role` claim
explicitly (c.Type == Claims.Role && Value in {Admin, SuperAdmin}). Works
regardless of how ClaimsIdentity was constructed.
- MoySkladImportController now uses [Authorize(Policy = "AdminAccess")].
- Add /api/_debug/whoami that echoes authType, roleClaimType, claims, and
IsInRole result — makes next auth issue trivial to diagnose.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause of the 404 on /api/admin/moysklad/test (and /api/me):
- AddIdentity<> sets DefaultChallengeScheme = IdentityConstants.ApplicationScheme
(cookies), so unauthorized API calls got 302 → /Account/Login → 404 instead of 401.
- Ephemeral OpenIddict keys (AddEphemeralSigningKey) regenerated on every API
restart, silently invalidating any JWT already stored in the browser.
Fixes:
- Explicitly set DefaultScheme / DefaultAuthenticateScheme / DefaultChallengeScheme
to OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme so [Authorize]
challenges now return 401 (axios interceptor can react + retry or redirect).
- Replace ephemeral RSA keys with a persistent dev RSA key stored in
src/food-market.api/App_Data/openiddict-dev-key.xml (gitignored). Generated on
first run, reused on subsequent starts. Dev tokens now survive API restarts.
Production must register proper X509 certificates via configuration.
- .gitignore: add App_Data/, *.pem, openiddict-dev-key.xml patterns.
- Web axios: on hard 401 with failed refresh, redirect to /login rather than
leaving the user stuck on a protected screen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Logo simplified to just "FOOD" (black) + "MARKET" (brand green) text — matches
the app-icon style without the distracting FM badge square.
MoySklad import page now shows actionable error text instead of generic
"Request failed with status code 404":
- 404 → "эндпоинт не существует, API не перезапущен после git pull"
- 401 → "сессия истекла, перелогинься"
- 403 → "нужна роль Admin или SuperAdmin"
- 502/503 → "МойСклад недоступен"
- Otherwise extracts body.error / error_description / title from response
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Infrastructure (foodmarket.Infrastructure.Integrations.MoySklad):
- MoySkladDtos: minimal shapes of products, folders, uom, prices, barcodes from JSON-API 1.2
- MoySkladClient: HttpClient wrapper with Bearer auth per call
- WhoAmIAsync (GET entity/organization) for connection test
- StreamProductsAsync (paginated 1000/page, IAsyncEnumerable)
- GetAllFoldersAsync (all product folders in one go)
- MoySkladImportService: orchestrates the full import
- Creates missing product folders with Path preserved
- Maps MoySklad VAT percent → local VatRate (fallback to default)
- Maps barcodes: ean13/ean8/code128/gtin/upca/upce → our BarcodeType enum
- Extracts retail price from salePrices (prefers "Розничная"), divides kopeck→major
- Extracts buyPrice → PurchasePrice
- Skips existing products by article OR primary barcode (unless overwrite flag set)
- Batch SaveChanges every 500 items to keep EF tracker light
- Returns counts + per-item error list
API: POST /api/admin/moysklad/test — returns org name if token valid
API: POST /api/admin/moysklad/import-products { token, overwriteExisting }
— Authorize(Roles = "Admin,SuperAdmin")
Web: /admin/import/moysklad page
- Amber notice: token is not persisted (request-scope only), how to create
a service token in moysklad.ru with read-only rights
- Test connection button + result banner
- Import button with "overwrite existing" checkbox
- Result panel with 4 counters + collapsible error list
Sidebar adds "Импорт" section with MoySklad link.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Extract brand colors from food-market-app/.../AppIcon/appicon.svg
(background #00B207, white FOOD, pale-green E8F5E9 MARKET)
- Add @theme custom colors in index.css:
--color-brand #00B207, --color-brand-hover #009305, --color-brand-dark #007605,
--color-brand-light #E8F5E9, --color-brand-tint #D7F2D9, --color-brand-foreground #FFFFFF
- Replace all violet-* Tailwind classes with var(--color-brand*) in:
LoginPage, Button, Field (input+checkbox), SearchBar, AppLayout (nav active state)
- New Logo component: FM square badge + "FOOD" + "MARKET" typography in brand colors
- Put Logo in sidebar header and on LoginPage
- Replace Vite default favicon with branded SVG (green square + FOOD MARKET)
- Page title "FOOD MARKET", theme-color meta tag for mobile browsers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Starter experience so the system is usable immediately after git clone → migrate → run.
DemoCatalogSeeder (Development only, runs once — skips if tenant has any products):
- 8 product groups: Напитки (Безалкогольные, Алкогольные), Молочка, Хлеб, Кондитерские,
Бакалея, Снеки — hierarchical Path computed
- 2 demo suppliers: ТОО «Продтрейд» (legal entity, KZ BIN, bank details), ИП Иванов (individual)
- 35 realistic KZ-market products with:
- Demo barcodes in 2xxx internal range (won't collide with real products)
- Retail price + purchase price at 72% of retail
- Country of origin (KZ / RU)
- Хлеб marked as 0% VAT (socially important goods in KZ)
- Сыр «Российский» marked as весовой
- Articles in kebab-case: DR-SOD-001, DAI-MLK-002, SW-CHO-001 etc.
Product form (full page /catalog/products/new and /:id, not modal):
- 5 sections: Основное / Классификация / Остатки и закупка / Цены / Штрихкоды
- Dropdowns for unit, VAT, group, country, supplier, currency via useLookups hooks
- Defaults pre-filled for new product (default VAT, base unit, KZT)
- Prices table: add/remove rows, pick price type + amount + currency
- Barcodes table: EAN-13/8/CODE128/UPC options, "primary" enforces single
- Server-side atomic save (existing Prices+Barcodes replaced on PUT)
Products page: row click → edit page, Add button → new page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop ReactQueryDevtools floating button (the "palm tree" in corner)
- Dashboard now shows: greeting with user name, stat cards, user profile
(name/email/roles/orgId), and roadmap
- Add amber banner when API calls fail (typical cause: API not restarted
after pulling new catalog code) with explicit fix instructions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Application layer:
- PagedRequest/PagedResult<T> with sane defaults (pageSize 50, max 500)
- CatalogDtos: read DTOs with joined names + input DTOs for upsert
- Product input supports nested Prices[] and Barcodes[] for atomic save
API controllers (api/catalog/…):
- countries, currencies (global, write requires SuperAdmin)
- vat-rates, units-of-measure, price-types, stores (write: Admin/Manager)
- retail-points (references Store, Admin/Manager write)
- product-groups: hierarchy with auto-computed Path, delete guarded against children/products
- counterparties: filter by kind (Supplier/Customer/Both), full join with Country
- products: includes joined lookups, filter by group/isService/isWeighed/isActive,
search by name/article/barcode, write replaces Prices+Barcodes atomically
Role semantics:
- SuperAdmin: mutates global references only
- Admin: mutates/deletes tenant references
- Manager: mutates tenant references (no delete on some)
- Storekeeper: can manage counterparties and products (but not delete)
All endpoints guarded by [Authorize]. Multi-tenant isolation via EF query filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Default launchSettings.json from `dotnet new web` picked random port 5039,
which doesn't match the Vite proxy target http://localhost:5081, so the
React app can't reach /connect/token during login.
- Fix http profile to 5081 (HTTPS to 5443)
- Remove IIS Express profile (Mac-only dev, not needed)
- Disable launchBrowser (we run Web separately)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>