|
|
@@ -0,0 +1,244 @@
|
|
|
+/**
|
|
|
+ * Query ShopRenter Edge Function
|
|
|
+ *
|
|
|
+ * Provides a simple query interface for ShopRenter orders and customers.
|
|
|
+ * Supports filtering by email or order_id with automatic token refresh.
|
|
|
+ *
|
|
|
+ * Authentication: Bearer token using internal_api_keys table
|
|
|
+ *
|
|
|
+ * Query Parameters:
|
|
|
+ * - stores_uuid: UUID of the store (required)
|
|
|
+ * - query_type: "order" or "customer" (required)
|
|
|
+ * - filter_by: "email" or "order_id" (required for orders)
|
|
|
+ * - filter_value: The value to filter by (required when filter_by is set)
|
|
|
+ */
|
|
|
+
|
|
|
+import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
|
|
|
+import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
|
|
|
+import {
|
|
|
+ requireInternalApiKey,
|
|
|
+ createInternalApiKeyErrorResponse
|
|
|
+} from '../_shared/internal-api-key-auth.ts';
|
|
|
+import {
|
|
|
+ fetchOrders,
|
|
|
+ fetchCustomers,
|
|
|
+ ShopRenterOrderFilters,
|
|
|
+ ShopRenterCustomerFilters
|
|
|
+} from '../_shared/shoprenter-client.ts';
|
|
|
+
|
|
|
+const corsHeaders = {
|
|
|
+ 'Access-Control-Allow-Origin': '*',
|
|
|
+ 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
|
|
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS'
|
|
|
+};
|
|
|
+
|
|
|
+// Initialize Supabase client
|
|
|
+const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
|
|
+const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
|
|
+const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
|
|
+
|
|
|
+serve(async (req: Request) => {
|
|
|
+ // Handle CORS preflight
|
|
|
+ if (req.method === 'OPTIONS') {
|
|
|
+ return new Response(null, { headers: corsHeaders });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Only allow GET requests
|
|
|
+ if (req.method !== 'GET') {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'Method not allowed. Use GET.' }),
|
|
|
+ {
|
|
|
+ status: 405,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Authenticate with internal API key
|
|
|
+ const authResult = await requireInternalApiKey(req, supabase, 'read_orders');
|
|
|
+
|
|
|
+ if (!authResult.valid) {
|
|
|
+ return createInternalApiKeyErrorResponse(authResult);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse query parameters
|
|
|
+ const url = new URL(req.url);
|
|
|
+ const stores_uuid = url.searchParams.get('stores_uuid');
|
|
|
+ const query_type = url.searchParams.get('query_type');
|
|
|
+ const filter_by = url.searchParams.get('filter_by');
|
|
|
+ const filter_value = url.searchParams.get('filter_value');
|
|
|
+
|
|
|
+ // Validate required parameters
|
|
|
+ if (!stores_uuid) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'stores_uuid parameter is required' }),
|
|
|
+ {
|
|
|
+ status: 400,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!query_type || !['order', 'customer'].includes(query_type)) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'query_type must be either "order" or "customer"' }),
|
|
|
+ {
|
|
|
+ status: 400,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validate filter parameters
|
|
|
+ if (filter_by && !filter_value) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'filter_value is required when filter_by is specified' }),
|
|
|
+ {
|
|
|
+ status: 400,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validate filter_by for each query_type
|
|
|
+ if (query_type === 'order' && filter_by && !['email', 'order_id'].includes(filter_by)) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'filter_by for orders must be "email" or "order_id"' }),
|
|
|
+ {
|
|
|
+ status: 400,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (query_type === 'customer' && filter_by && filter_by !== 'email') {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'filter_by for customers must be "email"' }),
|
|
|
+ {
|
|
|
+ status: 400,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify store exists and is ShopRenter
|
|
|
+ const { data: store, error: storeError } = await supabase
|
|
|
+ .from('stores')
|
|
|
+ .select('id, platform_name, store_name, data_access_permissions')
|
|
|
+ .eq('id', stores_uuid)
|
|
|
+ .eq('platform_name', 'shoprenter')
|
|
|
+ .single();
|
|
|
+
|
|
|
+ if (storeError || !store) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'ShopRenter store not found' }),
|
|
|
+ {
|
|
|
+ status: 404,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check permissions
|
|
|
+ const permissions = store.data_access_permissions as any;
|
|
|
+
|
|
|
+ if (query_type === 'order' && permissions && !permissions.allow_order_access) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'Order access not allowed for this store' }),
|
|
|
+ {
|
|
|
+ status: 403,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ if (query_type === 'customer' && permissions && !permissions.allow_customer_access) {
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({ error: 'Customer access not allowed for this store' }),
|
|
|
+ {
|
|
|
+ status: 403,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Execute query based on query_type
|
|
|
+ let result: any;
|
|
|
+
|
|
|
+ if (query_type === 'order') {
|
|
|
+ // Build filters for orders
|
|
|
+ const filters: ShopRenterOrderFilters = {};
|
|
|
+
|
|
|
+ if (filter_by === 'email' && filter_value) {
|
|
|
+ filters.email = filter_value;
|
|
|
+ } else if (filter_by === 'order_id' && filter_value) {
|
|
|
+ // Use innerId filter for customer-visible order numbers
|
|
|
+ filters.innerId = filter_value;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('[Query ShopRenter] Fetching orders with filters:', filters);
|
|
|
+
|
|
|
+ // Fetch orders from ShopRenter API (token refresh handled automatically)
|
|
|
+ const response = await fetchOrders(stores_uuid, 0, 25, filters);
|
|
|
+ const orders = Array.isArray(response) ? response : (response.items || response.data || response.orders || []);
|
|
|
+
|
|
|
+ result = {
|
|
|
+ success: true,
|
|
|
+ query_type: 'order',
|
|
|
+ filters_applied: {
|
|
|
+ filter_by,
|
|
|
+ filter_value
|
|
|
+ },
|
|
|
+ count: orders.length,
|
|
|
+ data: orders
|
|
|
+ };
|
|
|
+
|
|
|
+ } else if (query_type === 'customer') {
|
|
|
+ // Build filters for customers
|
|
|
+ const filters: ShopRenterCustomerFilters = {};
|
|
|
+
|
|
|
+ if (filter_by === 'email' && filter_value) {
|
|
|
+ filters.email = filter_value;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('[Query ShopRenter] Fetching customers with filters:', filters);
|
|
|
+
|
|
|
+ // Fetch customers from ShopRenter API (token refresh handled automatically)
|
|
|
+ const response = await fetchCustomers(stores_uuid, 0, 25, filters);
|
|
|
+ const customers = Array.isArray(response) ? response : (response.items || response.data || response.customers || []);
|
|
|
+
|
|
|
+ result = {
|
|
|
+ success: true,
|
|
|
+ query_type: 'customer',
|
|
|
+ filters_applied: {
|
|
|
+ filter_by,
|
|
|
+ filter_value
|
|
|
+ },
|
|
|
+ count: customers.length,
|
|
|
+ data: customers
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify(result),
|
|
|
+ {
|
|
|
+ status: 200,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('[Query ShopRenter] Error:', error);
|
|
|
+ return new Response(
|
|
|
+ JSON.stringify({
|
|
|
+ error: 'Internal server error',
|
|
|
+ message: error.message
|
|
|
+ }),
|
|
|
+ {
|
|
|
+ status: 500,
|
|
|
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+});
|