Skip to content

Technical Debt Assessment

server/storage.ts implements MemStorage using Map<number, T> for users, orders, integrations, and notifications. A hardcoded admin user is seeded on startup:

// Simplified — actual code in server/storage.ts
class MemStorage {
private users = new Map<number, User>();
private orders = new Map<number, Order>();
// ... all volatile, lost on restart
}

The Drizzle schema in shared/schema.ts defines proper tables (users, orders, integrations, notifications) but they are never connected to a real database. The schema exists as a type definition only.

server/auth.ts line 28 contains a direct password comparison that bypasses the scrypt hash:

if (supplied === 'admin123' && stored === '$2b$10$x5SafT...') {
return true; // bypasses scrypt verification
}

This means the admin password is effectively admin123 regardless of what the hash says.

The majority of route files — performance metrics, combo logs, tag classifications, tag analytics, tag groups, designers, asset ingest — have zero req.isAuthenticated() checks. Only a handful of legacy routes in server/routes.ts (integrations, orders, notifications) are protected. Combined with the split auth model, 40+ endpoints are effectively public.

Shopify data sync is manual-only. The syncShopifyData function in server/services/shopify-sync.ts is exported but never called on a schedule. The only way to refresh data is through a manual trigger — and the /api/performance-metrics/sync endpoint returns 501 Not Implemented.

The backend uses Passport.js local strategy with Express sessions (stored in MemoryStore), while the frontend uses Supabase Auth with JWT tokens. These two systems don’t share state:

  • Backend: req.isAuthenticated() checks Passport session
  • Frontend: useAuth() hook checks Supabase session via onAuthStateChange
  • API calls from the frontend don’t carry Supabase JWT to Express
  • Express sessions are volatile (MemoryStore, lost on restart)

Cloudflare Access eliminates this by providing a single identity plane.

Shopify API access is implemented independently in 6 locations:

  1. server/services/shopify-sync.ts — REST pagination for products/orders
  2. server/services/asset-ingest.ts — REST + Client Credentials Grant for metafields
  3. supabase/functions/sync-performance-data/ — REST + GraphQL Bulk Operations
  4. supabase/functions/sync-product-tags/ — REST pagination
  5. supabase/functions/generate-casestry-orders-csv/ — REST order fetch
  6. supabase/functions/shopify-order-lookup/ — REST with rate-limit retry

Each independently handles authentication, pagination, rate limiting, and error handling. The target architecture consolidates this into a single shared module.

Materialized views without refresh scheduling

Section titled “Materialized views without refresh scheduling”

mart.performance_metrics and mart.tag_performance_aggregate are PostgreSQL materialized views that require REFRESH MATERIALIZED VIEW calls. These are triggered only by:

  • Manual API call to /api/tag-analytics/refresh
  • Edge function execution (manual trigger)

There is no cron or scheduled refresh. The target system replaces materialized views with explicit table writes on a Cron Trigger schedule.

Despite the debt, several design decisions are sound and carry forward:

  • Warehouse layering (raw → core → mart) is the right pattern for Shopify data
  • Run tracking via raw.ingest_runs records every sync with duration, row counts, and error details
  • Idempotent upserts in several code paths (ON CONFLICT for dim_products, performance metrics; SHA-256 checksums for assets)
  • Checksum-based deduplication for assets prevents duplicate uploads
  • Tag-based analytics with GIN-indexed arrays enables flexible filtering
  • Combo ID convention (SP-IPH-2-WOMEN-001) provides human-readable, sortable identifiers
  • React Query for server state management is idiomatic and efficient
  • shadcn/ui components are well-structured and easily portable