Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Mercaline2024/Ecomdrop-ia-connector-2/llms.txt

Use this file to discover all available pages before exploring further.

Architecture

Ecomdrop IA Connector is built with modern, production-ready technologies designed for scalability, maintainability, and performance. This page provides a comprehensive overview of the system architecture.

System Overview

Tech Stack

Frontend

React Router v7

Modern routing with server-side rendering, data loading, and file-based routing

TypeScript

Type-safe development with full IDE support and compile-time error checking

Tailwind CSS

Utility-first CSS framework for rapid UI development

shadcn/ui

High-quality React components built on Radix UI primitives
Additional Frontend Libraries:
{
  "@shopify/app-bridge-react": "^4.2.4",
  "lucide-react": "^0.552.0",
  "react-phone-number-input": "^3.4.13",
  "class-variance-authority": "^0.7.1",
  "tailwind-merge": "^3.3.1",
  "tailwindcss-animate": "^1.0.7"
}

Backend

Node.js 20+

Modern JavaScript runtime with ESM support

React Router Server

Server-side rendering and API routes with type-safe data loading

Prisma ORM

Type-safe database client with migrations and schema management

MySQL 8.0

Production-grade relational database with ACID compliance
Backend Dependencies:
{
  "@shopify/shopify-app-react-router": "^1.0.0",
  "@shopify/shopify-app-session-storage-prisma": "^7.0.0",
  "@prisma/client": "^6.16.3",
  "mysql2": "^3.15.3"
}

DevOps

Docker

Containerization for consistent deployment across environments

Docker Compose

Multi-container orchestration for local development and production

Traefik

Reverse proxy and load balancer with automatic SSL

Portainer

Container management UI for monitoring and administration

Project Structure

ecomdrop-ia-connector/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ components/          # React components
β”‚   β”‚   β”œβ”€β”€ ui/             # shadcn/ui components
β”‚   β”‚   β”‚   β”œβ”€β”€ button.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ card.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ dialog.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ input.tsx
β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   └── modals/         # Custom modal components
β”‚   β”‚       β”œβ”€β”€ LoadingModal.tsx
β”‚   β”‚       └── SuccessModal.tsx
β”‚   β”œβ”€β”€ lib/                # Utility functions and API clients
β”‚   β”‚   β”œβ”€β”€ utils.ts
β”‚   β”‚   β”œβ”€β”€ ecomdrop.api.server.ts
β”‚   β”‚   └── shopify.order.server.ts
β”‚   β”œβ”€β”€ routes/             # React Router routes
β”‚   β”‚   β”œβ”€β”€ app._index.tsx         # Products page
β”‚   β”‚   β”œβ”€β”€ app.configuration.tsx  # Configuration page
β”‚   β”‚   β”œβ”€β”€ app.configuration.ai.tsx  # AI config page
β”‚   β”‚   β”œβ”€β”€ app.orders.tsx         # Orders page
β”‚   β”‚   β”œβ”€β”€ app.theme.tsx          # Theme management
β”‚   β”‚   β”œβ”€β”€ api.*.tsx              # API endpoints
β”‚   β”‚   └── webhooks.*.tsx         # Webhook handlers
β”‚   β”œβ”€β”€ shopify.server.ts   # Shopify authentication
β”‚   β”œβ”€β”€ db.server.ts        # Database connection
β”‚   └── globals.d.ts        # TypeScript declarations
β”œβ”€β”€ prisma/
β”‚   β”œβ”€β”€ schema.prisma       # Database schema (MySQL)
β”‚   └── migrations/         # Database migrations
β”œβ”€β”€ public/                 # Static assets
β”œβ”€β”€ Dockerfile             # Production Docker image
β”œβ”€β”€ docker-compose.yml     # Docker Compose configuration
β”œβ”€β”€ package.json           # Dependencies and scripts
β”œβ”€β”€ tsconfig.json          # TypeScript configuration
β”œβ”€β”€ tailwind.config.js     # Tailwind CSS configuration
└── vite.config.ts         # Vite bundler configuration

Database Schema

The application uses Prisma ORM with MySQL 8.0 for data persistence.

Schema Overview

// Shopify app sessions and authentication
model Session {
  id            String    @id
  shop          String    @db.VarChar(255)
  state         String    @db.VarChar(255)
  isOnline      Boolean   @default(false)
  scope         String?   @db.Text
  expires       DateTime?
  accessToken   String    @db.Text
  userId        BigInt?
  // ... additional fields
}

// Store-specific integration settings
model ShopConfiguration {
  id                      String   @id @default(uuid())
  shop                    String   @unique @db.VarChar(255)
  ecomdropApiKey          String?  @db.Text
  nuevoPedidoFlowId       String?  @db.VarChar(255)
  carritoAbandonadoFlowId String?  @db.VarChar(255)
  dropiStoreName          String?  @db.VarChar(255)
  dropiCountry            String?  @db.VarChar(10)
  dropiToken              String?  @db.Text
  createdAt               DateTime @default(now())
  updatedAt               DateTime @updatedAt
}

// Product mappings between platforms
model ProductAssociation {
  id                    String   @id @default(uuid())
  shop                  String   @db.VarChar(255)
  dropiProductId        String   @db.VarChar(255)
  shopifyProductId      String   @db.VarChar(255)
  dropiProductName      String?  @db.VarChar(500)
  shopifyProductTitle   String?  @db.VarChar(500)
  importType            String   @db.VarChar(50)
  dropiVariations       String?  @db.Text
  saveDropiName         Boolean  @default(true)
  saveDropiDescription  Boolean  @default(true)
  customPrice           String?  @db.VarChar(50)
  useSuggestedBarcode   Boolean  @default(false)
  saveDropiImages       Boolean  @default(true)
  createdAt             DateTime @default(now())
  updatedAt             DateTime @updatedAt
}

// AI assistant configuration per store
model AIConfiguration {
  id                      String   @id @default(uuid())
  shop                    String   @unique @db.VarChar(255)
  agentName               String?  @db.VarChar(255)
  companyName             String?  @db.VarChar(255)
  companyDescription      String?  @db.Text
  paymentMethods          String?  @db.Text  // JSON
  companyPolicies         String?  @db.Text
  faq                     String?  @db.Text  // JSON
  postSaleFaq             String?  @db.Text  // JSON
  rules                   String?  @db.Text  // JSON
  notifications           String?  @db.Text  // JSON
  createdAt               DateTime @default(now())
  updatedAt               DateTime @updatedAt
}
All configurations are isolated per Shopify store using the shop field, ensuring multi-tenant security.

Database Features

  • Indexes on frequently queried fields (shop, dropiProductId, shopifyProductId)
  • Unique constraints to prevent duplicate associations
  • Timestamps for tracking creation and updates
  • Text fields for storing JSON data (payment methods, FAQs, rules, notifications)
  • UUID primary keys for distributed systems compatibility

Authentication Flow

Shopify OAuth

// shopify.server.ts
import { shopifyApp } from "@shopify/shopify-app-react-router/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY,
  apiSecretKey: process.env.SHOPIFY_API_SECRET,
  apiVersion: ApiVersion.October25,
  scopes: process.env.SCOPES?.split(","),
  appUrl: process.env.SHOPIFY_APP_URL,
  authPathPrefix: "/auth",
  sessionStorage: new PrismaSessionStorage(prisma),
  distribution: AppDistribution.AppStore,
});

Authentication Steps

1

OAuth Initiation

User installs app from Shopify App Store or clicks β€œAdd app” in admin
2

Permission Request

Shopify displays requested scopes: read_products, write_products, read_orders, read_themes, write_themes
3

Token Exchange

After approval, Shopify sends authorization code, app exchanges it for access token
4

Session Storage

Access token and session data stored in MySQL via Prisma
5

App Bridge Connection

Embedded app connects to Shopify Admin using App Bridge

Protected Routes

All routes are protected with authentication middleware:
export async function loader({ request }: LoaderFunctionArgs) {
  const { session, admin } = await authenticate.admin(request);
  // Route logic here
}

API Integration Architecture

Ecomdrop API Client

// app/lib/ecomdrop.api.server.ts
const ECOMDROP_API_BASE = "https://panel.ecomdrop.app/api";

export async function getEcomdropFlows(apiKey: string) {
  const response = await fetch(`${ECOMDROP_API_BASE}/accounts/flows`, {
    method: "GET",
    headers: {
      "accept": "application/json",
      "X-ACCESS-TOKEN": apiKey,
    },
  });
  return response.json();
}

export async function triggerEcomdropFlow(
  apiKey: string,
  flowId: string,
  payload: any
) {
  const response = await fetch(
    `${ECOMDROP_API_BASE}/flows/${flowId}/trigger`,
    {
      method: "POST",
      headers: {
        "accept": "application/json",
        "X-ACCESS-TOKEN": apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    }
  );
  return response.json();
}

Dropi API Integration

Dropi integration uses Ecomdrop’s bot field API:
const DROPI_COUNTRY_FIELD_MAP: Record<string, string> = {
  'CO': '640597',  // Colombia
  'EC': '805359',  // Ecuador
  'CL': '665134',  // Chile
  'GT': '747995',  // Guatemala
  'MX': '641097',  // MΓ©xico
  'PA': '742965',  // PanamΓ‘
  'PE': '142979',  // PerΓΊ
  'PY': '240677',  // Paraguay
};

export async function validateAndSaveDropiIntegration(
  apiKey: string,
  country: string,
  dropiToken: string
) {
  const fieldId = DROPI_COUNTRY_FIELD_MAP[country];
  
  const response = await fetch(
    `${ECOMDROP_API_BASE}/accounts/bot_fields/${fieldId}`,
    {
      method: "POST",
      headers: {
        "accept": "application/json",
        "X-ACCESS-TOKEN": apiKey,
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams({ value: dropiToken }),
    }
  );
  
  return response.json();
}

Caching Strategy

Implements in-memory caching to prevent API rate limiting:
const flowsCache = new Map<string, { data: any; timestamp: number }>();
const CACHE_DURATION = 60000; // 1 minute

export async function getEcomdropFlows(apiKey: string) {
  // Check cache first
  const cached = flowsCache.get(apiKey);
  if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
    return { success: true, data: cached.data };
  }
  
  // Fetch from API and cache result
  const data = await fetchFromAPI();
  flowsCache.set(apiKey, { data, timestamp: Date.now() });
  return { success: true, data };
}

Webhook Processing

Registered Webhooks

// Webhooks are registered automatically by Shopify App package
const webhooks = [
  "orders/create",
  "draft_orders/create",
  "app/uninstalled",
  "app/scopes_update"
];

Webhook Handler Example

// app/routes/webhooks.orders.create.tsx
export async function action({ request }: ActionFunctionArgs) {
  const { topic, shop, session } = await authenticate.webhook(request);
  
  // Parse webhook payload
  const payload = await request.json();
  
  // Get store configuration
  const config = await db.shopConfiguration.findUnique({
    where: { shop }
  });
  
  // Trigger Ecomdrop flow if configured
  if (config?.nuevoPedidoFlowId && config?.ecomdropApiKey) {
    await triggerEcomdropFlow(
      config.ecomdropApiKey,
      config.nuevoPedidoFlowId,
      payload
    );
  }
  
  return json({ success: true });
}
All webhooks are verified using HMAC signatures to ensure they come from Shopify.

Docker Deployment

Dockerfile

FROM node:20-alpine
RUN apk add --no-cache openssl netcat-openbsd

EXPOSE 3000
WORKDIR /app
ENV NODE_ENV=production

# Install dependencies
COPY package.json package-lock.json* ./
RUN npm ci --omit=dev && npm cache clean --force

# Copy application files
COPY . .

# Build application
RUN npm run build

# Create entrypoint script that waits for MySQL
RUN echo '#!/bin/sh' > /app/docker-entrypoint.sh && \
    echo 'until nc -z mysql 3306; do' >> /app/docker-entrypoint.sh && \
    echo '  echo "Waiting for MySQL..."' >> /app/docker-entrypoint.sh && \
    echo '  sleep 2' >> /app/docker-entrypoint.sh && \
    echo 'done' >> /app/docker-entrypoint.sh && \
    echo 'exec npm run docker-start' >> /app/docker-entrypoint.sh && \
    chmod +x /app/docker-entrypoint.sh

CMD ["/app/docker-entrypoint.sh"]

Docker Compose Configuration

version: "3.8"

services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: shopify_app
      MYSQL_USER: shopify_user
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - EcomdropNet
    
  shopify_app:
    image: shopify-app_shopify_app:latest
    environment:
      DATABASE_URL: mysql://shopify_user:${MYSQL_PASSWORD}@mysql:3306/shopify_app
      SHOPIFY_API_KEY: ${SHOPIFY_API_KEY}
      SHOPIFY_API_SECRET: ${SHOPIFY_API_SECRET}
      SHOPIFY_APP_URL: https://connector.ecomdrop.io
      NODE_ENV: production
    networks:
      - EcomdropNet
    depends_on:
      - mysql
    deploy:
      labels:
        - traefik.enable=true
        - traefik.http.routers.shopify_app.rule=Host(`connector.ecomdrop.io`)
        - traefik.http.routers.shopify_app.tls.certresolver=letsencryptresolver

networks:
  EcomdropNet:
    external: true

volumes:
  mysql_data:

Production Deployment Features

Health Checks

MySQL health checks ensure database is ready before app starts

Automatic Restart

Failed containers automatically restart with exponential backoff

SSL/TLS

Automatic SSL certificate provisioning via Let’s Encrypt

Load Balancing

Traefik handles load balancing and request routing

Environment Variables

Required Variables

# Database
DATABASE_URL="mysql://user:password@host:3306/shopify_app"
MYSQL_ROOT_PASSWORD="secure_root_password"
MYSQL_PASSWORD="secure_user_password"

# Shopify API
SHOPIFY_API_KEY="your_api_key"
SHOPIFY_API_SECRET="your_api_secret"
SHOPIFY_APP_URL="https://your-app-url.com"
SCOPES="read_products,write_products,read_orders,read_themes,write_themes"

Optional Variables

# Theme Configuration
THEME_2_5_GIT_REPO="your-org/theme-repo"
THEME_2_5_GIT_BRANCH="main"
THEME_2_5_GIT_PROVIDER="github"
THEME_2_5_GIT_TOKEN="your_github_token"

# Custom Domain
SHOP_CUSTOM_DOMAIN="your-custom-domain.com"

Performance Optimizations

Server-Side Rendering

React Router v7 provides automatic server-side rendering:
// Data loaded on server before page render
export async function loader({ request }: LoaderFunctionArgs) {
  const { session, admin } = await authenticate.admin(request);
  
  const [configuration, associations] = await Promise.all([
    db.shopConfiguration.findUnique({ where: { shop: session.shop } }),
    db.productAssociation.findMany({ where: { shop: session.shop } })
  ]);
  
  return { configuration, associations };
}

Database Optimizations

  • Connection pooling via Prisma
  • Indexed queries on frequently accessed fields
  • Batch operations for bulk updates
  • Query optimization with Prisma’s query analyzer

Frontend Optimizations

  • Code splitting with React Router
  • Lazy loading of heavy components
  • Image optimization with CDN (Dropi images)
  • Debounced search to reduce API calls

Security Measures

  • API keys and tokens stored as encrypted text in database
  • HTTPS/TLS encryption for all API communications
  • Secure session storage with encrypted cookies
  • OAuth 2.0 for Shopify authentication
  • HMAC verification for webhook requests
  • Session-based authorization for API routes
  • Scoped access tokens with minimal permissions
  • TypeScript type checking at compile time
  • Prisma schema validation at database level
  • Form validation on both client and server
  • SQL injection prevention via parameterized queries
  • All data isolated per Shopify store
  • Unique indexes prevent cross-store data access
  • Session verification on every request

Monitoring & Logging

The application includes comprehensive logging:
// Structured logging for API calls
console.log("πŸ” Fetching flows from Ecomdrop API...");
console.log("πŸ“‘ API URL:", `${ECOMDROP_API_BASE}/accounts/flows`);
console.log("πŸ“Š Response status:", response.status);
console.log("βœ… Flows received:", data);

Log Categories

  • πŸ” API requests
  • πŸ“¦ Data operations
  • βœ… Success messages
  • ❌ Error messages
  • ⚠️ Warnings
  • πŸ“Š Status updates

Scalability Considerations

Horizontal Scaling

The app supports horizontal scaling:
  • Stateless design: No server-side state outside database
  • Session storage in MySQL: Shared across all app instances
  • Load balancing: Traefik distributes requests across replicas

Database Scaling

# MySQL configuration for production
command:
  - --innodb_buffer_pool_size=768M
  - --max_connections=200
  - --max_allowed_packet=256M

Caching Strategy

Multi-layer caching:
  1. In-memory cache for API responses (1 minute)
  2. Database query cache via Prisma
  3. Browser cache for static assets

Development Workflow

1

Local Development

npm install
npm run dev
Uses Shopify CLI for local development with tunnel
2

Database Migrations

npx prisma migrate dev --name add_new_field
npx prisma generate
Creates and applies database schema changes
3

Type Checking

npm run typecheck
Ensures TypeScript type safety across the codebase
4

Build

npm run build
Compiles application for production
5

Docker Build

docker build -t ecomdrop-ia-connector:latest .
Creates production Docker image
6

Deploy

docker-compose up -d
Deploys to production with Docker Compose

Migration Path

The application has migrated from SQLite to MySQL:
If upgrading from SQLite, run migrations carefully:
# Backup SQLite data first
npx prisma migrate deploy

Migration Benefits

  • Better performance under high load
  • ACID compliance for data integrity
  • Concurrent access support
  • Production-ready reliability

API Reference

For detailed API documentation, see the Features page.

View Features

Explore all features

Quick Start

Get started quickly

API Reference

API documentation