Function bodies 299 total
deleteMeal function · typescript · L171-L178 (8 LOC)src/services/mealLog.ts
export async function deleteMeal(id: string): Promise<void> {
if (id.startsWith('pending-')) {
await removePendingItem(id);
return;
}
const { error } = await supabase.from('daily_logs').delete().eq('id', id);
if (error) console.error('Error deleting meal:', error);
}checkStreakMilestone function · typescript · L180-L203 (24 LOC)src/services/mealLog.ts
async function checkStreakMilestone(): Promise<void> {
try {
const { data: plan } = await supabase
.from('user_plans')
.select('daily_calories')
.eq('is_active', true)
.order('created_at', { ascending: false })
.limit(1)
.single();
if (!plan?.daily_calories) return;
const stats = await getOverviewStats(plan.daily_calories);
if (stats.streak > 0) {
await checkAndRecordMilestone(stats.streak);
const userId = await getUserId();
if (userId) {
sendMilestoneNotifications(userId, stats.streak).catch(() => {});
}
}
} catch {
// Non-critical: silently fail
}
}getDailyTotals function · typescript · L205-L217 (13 LOC)src/services/mealLog.ts
export async function getDailyTotals(date?: Date): Promise<DailyMacroTotals> {
const meals = date ? await getMealsForDate(date) : await getTodaysMeals();
return meals.reduce<DailyMacroTotals>(
(acc, m) => ({
calories: acc.calories + m.calories,
protein: acc.protein + m.protein,
carbs: acc.carbs + m.carbs,
fat: acc.fat + m.fat,
mealCount: acc.mealCount + 1,
}),
{ calories: 0, protein: 0, carbs: 0, fat: 0, mealCount: 0 },
);
}getUserId function · typescript · L17-L20 (4 LOC)src/services/notifications.ts
async function getUserId(): Promise<string | null> {
const { data: { user } } = await supabase.auth.getUser();
return user?.id ?? null;
}registerForPushNotifications function · typescript · L22-L48 (27 LOC)src/services/notifications.ts
export async function registerForPushNotifications(): Promise<string | null> {
const messaging = getMessaging();
const authStatus = await requestPermission(messaging);
const enabled =
authStatus === AuthorizationStatus.AUTHORIZED ||
authStatus === AuthorizationStatus.PROVISIONAL;
if (!enabled) return null;
const fcmToken = await getToken(messaging);
if (!fcmToken) return null;
const userId = await getUserId();
if (!userId) return null;
// Remove stale tokens: if this device's token was registered under a
// different user (e.g. after sign-out / sign-in with another account),
// delete the old entry so the previous user stops receiving pushes here.
await supabase.rpc('claim_device_token', { p_fcm_token: fcmToken });
await supabase.from('device_tokens').upsert(
{ user_id: userId, fcm_token: fcmToken, platform: 'ios', updated_at: new Date().toISOString() },
{ onConflict: 'user_id,fcm_token' },
);
return fcmToken;
}unregisterPushToken function · typescript · L50-L66 (17 LOC)src/services/notifications.ts
export async function unregisterPushToken(): Promise<void> {
try {
const fcmToken = await getToken(getMessaging());
if (!fcmToken) return;
const userId = await getUserId();
if (!userId) return;
await supabase
.from('device_tokens')
.delete()
.eq('user_id', userId)
.eq('fcm_token', fcmToken);
} catch {
// Best-effort cleanup
}
}onTokenRefresh function · typescript · L68-L70 (3 LOC)src/services/notifications.ts
export function onTokenRefresh(callback: (token: string) => void): () => void {
return firebaseOnTokenRefresh(getMessaging(), callback);
}Powered by Repobility — scan your code at https://repobility.com
upsertToken function · typescript · L72-L80 (9 LOC)src/services/notifications.ts
export async function upsertToken(fcmToken: string): Promise<void> {
const userId = await getUserId();
if (!userId) return;
await supabase.from('device_tokens').upsert(
{ user_id: userId, fcm_token: fcmToken, platform: 'ios', updated_at: new Date().toISOString() },
{ onConflict: 'user_id,fcm_token' },
);
}sendNotification function · typescript · L82-L93 (12 LOC)src/services/notifications.ts
export async function sendNotification(
type: NotificationType,
recipientId: string,
data: Record<string, string>,
): Promise<void> {
await withRetry(
() => supabase.functions.invoke('send-notification', {
body: { type, recipientId, data },
}),
{ maxRetries: 1 },
);
}sendMilestoneNotifications function · typescript · L95-L102 (8 LOC)src/services/notifications.ts
export async function sendMilestoneNotifications(
userId: string,
streakDays: number,
): Promise<void> {
await supabase.functions.invoke('send-notification', {
body: { type: 'streak_milestone', userId, data: { streakDays: String(streakDays) } },
});
}todayKey function · typescript · L36-L39 (4 LOC)src/services/offlineStore.ts
function todayKey(): string {
const d = new Date();
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}nextPendingId function · typescript · L42-L44 (3 LOC)src/services/offlineStore.ts
function nextPendingId(): string {
return `pending-${Date.now()}-${++pendingIdCounter}`;
}getUserId function · typescript · L46-L49 (4 LOC)src/services/offlineStore.ts
async function getUserId(): Promise<string | null> {
const { data: { user } } = await supabase.auth.getUser();
return user?.id ?? null;
}getQueue function · typescript · L53-L60 (8 LOC)src/services/offlineStore.ts
export async function getQueue(): Promise<QueueItem[]> {
try {
const json = await AsyncStorage.getItem(QUEUE_KEY);
return json ? JSON.parse(json) : [];
} catch {
return [];
}
}saveQueue function · typescript · L62-L64 (3 LOC)src/services/offlineStore.ts
async function saveQueue(queue: QueueItem[]): Promise<void> {
await AsyncStorage.setItem(QUEUE_KEY, JSON.stringify(queue));
}All rows scored by the Repobility analyzer (https://repobility.com)
enqueueMealLog function · typescript · L70-L95 (26 LOC)src/services/offlineStore.ts
export async function enqueueMealLog(
data: MealData,
imageBase64?: string,
): Promise<MealEntry> {
const id = nextPendingId();
const now = Date.now();
const item: MealQueueItem = {
id,
type: 'meal',
payload: { data, imageBase64 },
createdAt: now,
};
const queue = await getQueue();
queue.push(item);
await saveQueue(queue);
return {
id,
...data,
timestamp: now,
imageBase64,
imageUri: imageBase64 ? `data:image/jpeg;base64,${imageBase64}` : undefined,
};
}enqueueWeightLog function · typescript · L98-L108 (11 LOC)src/services/offlineStore.ts
export async function enqueueWeightLog(weightKg: number): Promise<void> {
const item: WeightQueueItem = {
id: nextPendingId(),
type: 'weight',
payload: { weightKg },
createdAt: Date.now(),
};
const queue = await getQueue();
queue.push(item);
await saveQueue(queue);
}removePendingItem function · typescript · L111-L114 (4 LOC)src/services/offlineStore.ts
export async function removePendingItem(id: string): Promise<void> {
const queue = await getQueue();
await saveQueue(queue.filter(q => q.id !== id));
}getPendingMealsForToday function · typescript · L117-L134 (18 LOC)src/services/offlineStore.ts
export async function getPendingMealsForToday(): Promise<MealEntry[]> {
const queue = await getQueue();
const startOfDay = new Date();
startOfDay.setHours(0, 0, 0, 0);
const dayStart = startOfDay.getTime();
return queue
.filter((q): q is MealQueueItem => q.type === 'meal' && q.createdAt >= dayStart)
.map(q => ({
id: q.id,
...q.payload.data,
timestamp: q.createdAt,
imageBase64: q.payload.imageBase64,
imageUri: q.payload.imageBase64
? `data:image/jpeg;base64,${q.payload.imageBase64}`
: undefined,
}));
}cacheTodaysMeals function · typescript · L219-L222 (4 LOC)src/services/offlineStore.ts
export async function cacheTodaysMeals(meals: MealEntry[]): Promise<void> {
const payload: CachedMeals = { dateKey: todayKey(), meals };
await AsyncStorage.setItem(CACHE_MEALS_KEY, JSON.stringify(payload));
}getCachedTodaysMeals function · typescript · L224-L234 (11 LOC)src/services/offlineStore.ts
export async function getCachedTodaysMeals(): Promise<MealEntry[] | null> {
try {
const json = await AsyncStorage.getItem(CACHE_MEALS_KEY);
if (!json) return null;
const cached: CachedMeals = JSON.parse(json);
if (cached.dateKey !== todayKey()) return null;
return cached.meals;
} catch {
return null;
}
}cacheProfile function · typescript · L236-L238 (3 LOC)src/services/offlineStore.ts
export async function cacheProfile(profile: UserProfile): Promise<void> {
await AsyncStorage.setItem(CACHE_PROFILE_KEY, JSON.stringify(profile));
}getCachedProfile function · typescript · L240-L247 (8 LOC)src/services/offlineStore.ts
export async function getCachedProfile(): Promise<UserProfile | null> {
try {
const json = await AsyncStorage.getItem(CACHE_PROFILE_KEY);
return json ? JSON.parse(json) : null;
} catch {
return null;
}
}Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
cacheSavedMeals function · typescript · L249-L251 (3 LOC)src/services/offlineStore.ts
export async function cacheSavedMeals(meals: SavedMeal[]): Promise<void> {
await AsyncStorage.setItem(CACHE_SAVED_MEALS_KEY, JSON.stringify(meals));
}getCachedSavedMeals function · typescript · L253-L260 (8 LOC)src/services/offlineStore.ts
export async function getCachedSavedMeals(): Promise<SavedMeal[] | null> {
try {
const json = await AsyncStorage.getItem(CACHE_SAVED_MEALS_KEY);
return json ? JSON.parse(json) : null;
} catch {
return null;
}
}clearOfflineData function · typescript · L262-L266 (5 LOC)src/services/offlineStore.ts
export async function clearOfflineData(): Promise<void> {
await AsyncStorage.multiRemove([
QUEUE_KEY, CACHE_MEALS_KEY, CACHE_PROFILE_KEY, CACHE_SAVED_MEALS_KEY,
]);
}getUserId function · typescript · L17-L20 (4 LOC)src/services/profileService.ts
async function getUserId(): Promise<string | null> {
const { data: { user } } = await supabase.auth.getUser();
return user?.id ?? null;
}validateUsername function · typescript · L26-L36 (11 LOC)src/services/profileService.ts
export function validateUsername(username: string, skipProfanity = false): string | null {
if (!username) return 'Username is required';
if (username.length < 3) return 'Username must be at least 3 characters';
if (username.length > 20) return 'Username must be 20 characters or fewer';
if (!USERNAME_REGEX.test(username)) {
return 'Only letters, numbers, and underscores allowed';
}
if (/^[_0-9]/.test(username)) return 'Username must start with a letter';
if (!skipProfanity && containsProhibitedWord(username)) return 'This username is not available';
return null;
}checkUsernameAvailable function · typescript · L38-L53 (16 LOC)src/services/profileService.ts
export async function checkUsernameAvailable(username: string): Promise<boolean> {
const userId = await getUserId();
const { data, error } = await supabase
.from('public_profiles')
.select('id')
.ilike('username', username)
.limit(1)
.single();
if (error && error.code === 'PGRST116') return true; // no rows
if (error) {
console.error('Error checking username:', error);
return false;
}
return data?.id === userId;
}searchUsers function · typescript · L57-L75 (19 LOC)src/services/profileService.ts
export async function searchUsers(query: string): Promise<PublicProfile[]> {
const userId = await getUserId();
if (!userId || query.length < 2) return [];
try {
const { data, error } = await supabase
.from('public_profiles')
.select('id, username, display_name, profile_picture_url, bio, is_private')
.or(`username.ilike.%${query}%,display_name.ilike.%${query}%`)
.neq('id', userId)
.limit(10);
if (error) throw error;
return (data ?? []).map(rowToPublicProfile);
} catch (error) {
console.error('Error searching users:', error);
return [];
}
}adminSearchUsers function · typescript · L84-L86 (3 LOC)src/services/profileService.ts
export async function adminSearchUsers(
query: string,
): Promise<{ data: AdminProfile[]; error: string | null }> {Repobility · open methodology · https://repobility.com/research/
getPublicProfile function · typescript · L115-L129 (15 LOC)src/services/profileService.ts
export async function getPublicProfile(userId: string): Promise<PublicProfile | null> {
try {
const { data, error } = await supabase
.from('public_profiles')
.select('id, username, display_name, profile_picture_url, bio, is_private')
.eq('id', userId)
.single();
if (error || !data) return null;
return rowToPublicProfile(data);
} catch (error) {
console.error('Error fetching public profile:', error);
return null;
}
}rowToPublicProfile function · typescript · L131-L140 (10 LOC)src/services/profileService.ts
function rowToPublicProfile(row: any): PublicProfile {
return {
id: row.id,
username: row.username || null,
displayName: row.display_name || null,
profilePictureUrl: row.profile_picture_url || null,
bio: row.bio || null,
isPrivate: row.is_private ?? false,
};
}base64ToArrayBuffer function · typescript · L144-L152 (9 LOC)src/services/profileService.ts
function base64ToArrayBuffer(base64: string): ArrayBuffer {
const binaryString = atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}uploadProfilePicture function · typescript · L154-L198 (45 LOC)src/services/profileService.ts
export async function uploadProfilePicture(base64Data: string): Promise<string> {
const userId = await getUserId();
if (!userId) throw new Error('Not authenticated');
const timestamp = Date.now();
const filePath = `${userId}/${timestamp}.jpg`;
const arrayBuffer = base64ToArrayBuffer(base64Data);
const { error: uploadError } = await supabase.storage
.from('profile-pictures')
.upload(filePath, arrayBuffer, { contentType: 'image/jpeg', upsert: true });
if (uploadError) throw uploadError;
const { data: urlData } = supabase.storage
.from('profile-pictures')
.getPublicUrl(filePath);
const publicUrl = urlData.publicUrl;
await supabase.from('profiles').update({
profile_picture_url: publicUrl,
updated_at: new Date().toISOString(),
}).eq('id', userId);
// Clean up old pictures
try {
const { data: files } = await supabase.storage
.from('profile-pictures')
.list(userId);
if (files && files.length > 1) {
const oldFiupdateProfileFields function · typescript · L202-L220 (19 LOC)src/services/profileService.ts
export async function updateProfileFields(
fields: Partial<{
username: string;
display_name: string | null;
profile_picture_url: string | null;
bio: string | null;
is_private: boolean;
}>,
): Promise<void> {
const userId = await getUserId();
if (!userId) throw new Error('Not authenticated');
const { error } = await supabase
.from('profiles')
.update({ ...fields, updated_at: new Date().toISOString() })
.eq('id', userId);
if (error) throw error;
}suggestUsernameFromEmail function · typescript · L222-L225 (4 LOC)src/services/profileService.ts
export function suggestUsernameFromEmail(email: string): string {
const local = email.split('@')[0] || '';
return local.replace(/[^a-zA-Z0-9_]/g, '_').slice(0, 20).toLowerCase();
}getUserId function · typescript · L50-L53 (4 LOC)src/services/reportData.ts
async function getUserId(): Promise<string | null> {
const { data: { user } } = await supabase.auth.getUser();
return user?.id ?? null;
}toLocalDateString function · typescript · L56-L61 (6 LOC)src/services/reportData.ts
function toLocalDateString(date: Date): string {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}Powered by Repobility — scan your code at https://repobility.com
getDailySummaries function · typescript · L70-L111 (42 LOC)src/services/reportData.ts
export async function getDailySummaries(days: number): Promise<DailySummary[]> {
const userId = await getUserId();
if (!userId) return [];
let query = supabase
.from('daily_logs')
.select('logged_at, calories, protein_g, carbs_g, fat_g')
.eq('user_id', userId)
.order('logged_at', { ascending: true });
if (days > 0) {
const start = new Date();
start.setHours(0, 0, 0, 0);
start.setDate(start.getDate() - (days - 1));
query = query.gte('logged_at', start.toISOString());
}
const { data: rows, error } = await query;
if (error || !rows) return [];
const map = new Map<string, DailySummary>();
for (const r of rows) {
const dateKey = toLocalDateString(new Date(r.logged_at));
const existing = map.get(dateKey) ?? {
date: dateKey,
calories: 0,
protein: 0,
carbs: 0,
fat: 0,
mealCount: 0,
};
existing.calories += r.calories || 0;
existing.protein += Number(r.protein_g) || 0;
existing.carbgetOverviewStats function · typescript · L118-L175 (58 LOC)src/services/reportData.ts
export async function getOverviewStats(goalCalories: number): Promise<OverviewStats> {
const summaries = await getDailySummaries(0);
const dateSet = new Set(summaries.map(s => s.date));
const daysTracked = dateSet.size;
// Streak: walk backwards from today.
// Today counts if it has any logged meals; then check consecutive prior days.
let streak = 0;
const cursor = new Date();
cursor.setHours(0, 0, 0, 0);
const todayKey = toLocalDateString(cursor);
if (dateSet.has(todayKey)) {
// Today has meals — include it and walk backwards
streak++;
cursor.setDate(cursor.getDate() - 1);
while (dateSet.has(toLocalDateString(cursor))) {
streak++;
cursor.setDate(cursor.getDate() - 1);
}
} else {
// Today has no meals yet — start from yesterday
cursor.setDate(cursor.getDate() - 1);
while (dateSet.has(toLocalDateString(cursor))) {
streak++;
cursor.setDate(cursor.getDate() - 1);
}
}
// Adherence: current calendar wegetWeightLogs function · typescript · L178-L203 (26 LOC)src/services/reportData.ts
export async function getWeightLogs(days?: number): Promise<WeightLog[]> {
const userId = await getUserId();
if (!userId) return [];
let query = supabase
.from('weight_logs')
.select('id, weight_kg, logged_at')
.eq('user_id', userId)
.order('logged_at', { ascending: true });
if (days && days > 0) {
const start = new Date();
start.setHours(0, 0, 0, 0);
start.setDate(start.getDate() - (days - 1));
query = query.gte('logged_at', start.toISOString());
}
const { data: rows, error } = await query;
if (error || !rows) return [];
return rows.map(r => ({
id: r.id,
weightKg: Number(r.weight_kg),
loggedAt: r.logged_at,
}));
}logWeight function · typescript · L206-L241 (36 LOC)src/services/reportData.ts
export async function logWeight(weightKg: number): Promise<void> {
const userId = await getUserId();
if (!userId) throw new Error('Not authenticated');
try {
const todayStart = new Date();
todayStart.setHours(0, 0, 0, 0);
const tomorrowStart = new Date(todayStart);
tomorrowStart.setDate(tomorrowStart.getDate() + 1);
await supabase
.from('weight_logs')
.delete()
.eq('user_id', userId)
.gte('logged_at', todayStart.toISOString())
.lt('logged_at', tomorrowStart.toISOString());
const { error } = await supabase
.from('weight_logs')
.insert({ user_id: userId, weight_kg: weightKg });
if (error) throw error;
} catch (err) {
const msg = String((err as any)?.message ?? err).toLowerCase();
const isNetwork =
msg.includes('network') ||
msg.includes('fetch') ||
msg.includes('timeout') ||
msg.includes('failed to fetch');
if (isNetwork) {
await enqueueWeightLog(weightKg);
regetFriendStats function · typescript · L244-L265 (22 LOC)src/services/reportData.ts
export async function getFriendStats(friendId: string): Promise<FriendStats | null> {
const { data, error } = await supabase.rpc('get_friend_stats', { p_friend_id: friendId });
if (error || !data) return null;
return {
streak: data.streak ?? 0,
daysTracked: data.days_tracked ?? 0,
adherencePct: data.adherence_pct ?? 0,
goalType: data.goal_type ?? null,
goalCalories: data.goal_calories ?? null,
goalProtein: data.goal_protein ?? null,
goalCarbs: data.goal_carbs ?? null,
goalFat: data.goal_fat ?? null,
startWeightKg: data.start_weight_kg ?? null,
currentWeightKg: data.current_weight_kg ?? null,
weightChangeKg: data.weight_change_kg ?? null,
avgCalories: data.avg_calories ?? 0,
avgProtein: data.avg_protein ?? 0,
avgCarbs: data.avg_carbs ?? 0,
avgFat: data.avg_fat ?? 0,
avgDays: data.avg_days ?? 0,
};
}getUserId function · typescript · L26-L29 (4 LOC)src/services/savedMeals.ts
async function getUserId(): Promise<string | null> {
const { data: { user } } = await supabase.auth.getUser();
return user?.id ?? null;
}rowToSavedMeal function · typescript · L31-L43 (13 LOC)src/services/savedMeals.ts
function rowToSavedMeal(row: any): SavedMeal {
return {
id: row.id,
name: row.meal_name,
calories: row.calories || 0,
protein: Number(row.protein_g) || 0,
carbs: Number(row.carbs_g) || 0,
fat: Number(row.fat_g) || 0,
notes: row.notes,
imageUrl: row.image_url,
createdAt: new Date(row.created_at).getTime(),
};
}getSavedMeals function · typescript · L47-L68 (22 LOC)src/services/savedMeals.ts
export async function getSavedMeals(): Promise<SavedMeal[]> {
const userId = await getUserId();
if (!userId) return [];
try {
const { data: rows, error } = await supabase
.from('saved_meals')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: false });
if (error) throw error;
const meals = (rows ?? []).map(rowToSavedMeal);
await cacheSavedMeals(meals);
return meals;
} catch (error) {
console.error('Error loading saved meals:', error);
const cached = await getCachedSavedMeals();
return cached ?? [];
}
}All rows scored by the Repobility analyzer (https://repobility.com)
saveMeal function · typescript · L70-L95 (26 LOC)src/services/savedMeals.ts
export async function saveMeal(input: SavedMealInput): Promise<SavedMeal> {
const userId = await getUserId();
if (!userId) throw new Error('Not authenticated');
const imageUrl = input.imageBase64
? `data:image/jpeg;base64,${input.imageBase64}`
: null;
const { data: row, error } = await supabase
.from('saved_meals')
.insert({
user_id: userId,
meal_name: input.name,
calories: input.calories,
protein_g: input.protein,
carbs_g: input.carbs,
fat_g: input.fat,
notes: input.notes || null,
image_url: imageUrl,
})
.select()
.single();
if (error || !row) throw error || new Error('Failed to save meal');
return rowToSavedMeal(row);
}updateSavedMeal function · typescript · L97-L126 (30 LOC)src/services/savedMeals.ts
export async function updateSavedMeal(
id: string,
input: SavedMealInput,
): Promise<SavedMeal> {
const imageUrl = input.imageBase64
? `data:image/jpeg;base64,${input.imageBase64}`
: undefined;
const updateObj: Record<string, unknown> = {
meal_name: input.name,
calories: input.calories,
protein_g: input.protein,
carbs_g: input.carbs,
fat_g: input.fat,
notes: input.notes || null,
};
if (imageUrl !== undefined) {
updateObj.image_url = imageUrl;
}
const { data: row, error } = await supabase
.from('saved_meals')
.update(updateObj)
.eq('id', id)
.select()
.single();
if (error || !row) throw error || new Error('Failed to update saved meal');
return rowToSavedMeal(row);
}deleteSavedMeal function · typescript · L128-L131 (4 LOC)src/services/savedMeals.ts
export async function deleteSavedMeal(id: string): Promise<void> {
const { error } = await supabase.from('saved_meals').delete().eq('id', id);
if (error) console.error('Error deleting saved meal:', error);
}