All files / lib/supabase helpers.ts

97.87% Statements 46/47
100% Branches 43/43
92.85% Functions 13/14
97.82% Lines 45/46

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174                                              29x       35x                   29x 5x 4x         4x             1x           29x 205x           29x 251x 17x     234x 50x   197x 197x 197x           184x           29x               5x   5x 3x   5x 2x   5x 1x   5x 1x   5x 1x   5x 1x     5x                           29x 14x                 29x     2x 2x           29x     2x 2x           29x   198x     101x     51x            
/**
 * Supabase 辅助函数
 * 提供常用的数据库操作封装
 */
 
import { logger as appLogger } from '@/src/utils/logger';
 
import { supabase } from './client';
 
import type { PostgrestError } from '@supabase/supabase-js';
 
/**
 * Supabase 响应类型
 */
export interface SupabaseResponse<T> {
  data: T | null;
  error: PostgrestError | null;
  success: boolean;
}
 
/**
 * 包装Supabase响应为统一格式
 */
export const wrapResponse = <T>(
  data: T | null,
  error: PostgrestError | null
): SupabaseResponse<T> => {
  return {
    data,
    error,
    success: !error && data !== null,
  };
};
 
/**
 * 处理Supabase错误
 */
export const handleSupabaseError = (error: PostgrestError | null, context: string) => {
  if (error) {
    appLogger.error(`Supabase错误 [${context}]`, new Error(error.message), {
      code: error.code,
      details: error.details,
      hint: error.hint,
    });
    return {
      message: error.message,
      code: error.code,
      details: error.details,
      hint: error.hint,
    };
  }
  return null;
};
 
/**
 * 转换snake_case到camelCase
 */
export const snakeToCamel = (str: string): string => {
  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
};
 
/**
 * 递归转换对象的key从snake_case到camelCase
 */
export const convertKeysToCamel = (obj: unknown): unknown => {
  if (Array.isArray(obj)) {
    return obj.map(convertKeysToCamel);
  }
 
  if (obj !== null && typeof obj === 'object' && !(obj instanceof Date)) {
    return Object.keys(obj).reduce(
      (acc, key) => {
        const camelKey = snakeToCamel(key);
        acc[camelKey] = convertKeysToCamel((obj as Record<string, unknown>)[key]);
        return acc;
      },
      {} as Record<string, unknown>
    );
  }
 
  return obj;
};
 
/**
 * 构建percentData从营养成分字段
 */
export const buildPercentData = (data: {
  crude_protein?: number;
  crude_fat?: number;
  carbohydrates?: number;
  crude_fiber?: number;
  crude_ash?: number;
  others?: number;
}) => {
  const percentData: Record<string, number> = {};
 
  if (data.crude_protein !== null && data.crude_protein !== undefined) {
    percentData.protein = data.crude_protein;
  }
  if (data.crude_fat !== null && data.crude_fat !== undefined) {
    percentData.fat = data.crude_fat;
  }
  if (data.carbohydrates !== null && data.carbohydrates !== undefined) {
    percentData.carbohydrates = data.carbohydrates;
  }
  if (data.crude_fiber !== null && data.crude_fiber !== undefined) {
    percentData.fiber = data.crude_fiber;
  }
  if (data.crude_ash !== null && data.crude_ash !== undefined) {
    percentData.ash = data.crude_ash;
  }
  if (data.others !== null && data.others !== undefined) {
    percentData.others = data.others;
  }
 
  return Object.keys(percentData).length > 0 ? percentData : null;
};
 
/**
 * 分页参数
 */
export interface PaginationParams {
  page?: number;
  pageSize?: number;
}
 
/**
 * 计算分页的offset和limit
 */
export const calculatePagination = ({ page = 1, pageSize = 20 }: PaginationParams) => {
  return {
    from: (page - 1) * pageSize,
    to: page * pageSize - 1,
  };
};
 
/**
 * 获取当前用户ID
 */
export const getCurrentUserId = async () => {
  const {
    data: { user },
  } = await supabase.auth.getUser();
  return user?.id || null;
};
 
/**
 * 检查用户是否已认证
 */
export const isAuthenticated = async () => {
  const {
    data: { session },
  } = await supabase.auth.getSession();
  return !!session;
};
 
/**
 * 日志工具(使用统一的 logger)
 */
export const logger = {
  query: (table: string, operation: string, params?: unknown) => {
    appLogger.debug('Supabase Query', { table, operation, params: params || null });
  },
  success: (table: string, operation: string, count?: number | string) => {
    appLogger.info('Supabase Success', { table, operation, count });
  },
  error: (table: string, operation: string, error: unknown) => {
    appLogger.error('Supabase Error', error as Error, { table, operation });
  },
  info: (message: string, data?: Record<string, unknown>) => {
    appLogger.info(message, data as any);
  },
};