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

Build docs developers (and LLMs) love