341 lines
9.9 KiB
TypeScript
341 lines
9.9 KiB
TypeScript
import { Context } from 'hono';
|
|
import supabase from '../utils/supabase';
|
|
|
|
// Get all influencers with filtering and pagination
|
|
const getInfluencers = async (c: Context) => {
|
|
try {
|
|
const {
|
|
platform,
|
|
limit = '10',
|
|
offset = '0',
|
|
min_followers,
|
|
max_followers,
|
|
sort_by = 'followers_count',
|
|
sort_order = 'desc'
|
|
} = c.req.query();
|
|
|
|
let query = supabase
|
|
.from('influencers')
|
|
.select(`
|
|
influencer_id,
|
|
name,
|
|
platform,
|
|
profile_url,
|
|
external_id,
|
|
followers_count,
|
|
video_count,
|
|
platform_count,
|
|
created_at,
|
|
updated_at
|
|
`, { count: 'exact' });
|
|
|
|
// Apply filters
|
|
if (platform) {
|
|
query = query.eq('platform', platform);
|
|
}
|
|
if (min_followers) {
|
|
query = query.gte('followers_count', Number(min_followers));
|
|
}
|
|
if (max_followers) {
|
|
query = query.lte('followers_count', Number(max_followers));
|
|
}
|
|
|
|
// Apply sorting
|
|
if (sort_by && ['followers_count', 'video_count', 'created_at'].includes(sort_by)) {
|
|
query = query.order(sort_by, { ascending: sort_order === 'asc' });
|
|
}
|
|
|
|
// Apply pagination
|
|
query = query.range(Number(offset), Number(offset) + Number(limit) - 1);
|
|
|
|
const { data: influencers, error, count } = await query;
|
|
|
|
if (error) {
|
|
console.error('Error fetching influencers:', error);
|
|
return c.json({ error: error.message }, 500);
|
|
}
|
|
|
|
return c.json({
|
|
influencers,
|
|
count,
|
|
limit: Number(limit),
|
|
offset: Number(offset)
|
|
});
|
|
} catch (error) {
|
|
console.error('Error in getInfluencers:', error);
|
|
return c.json({ error: 'Internal server error' }, 500);
|
|
}
|
|
};
|
|
|
|
// Get a specific influencer by ID
|
|
const getInfluencerById = async (c: Context) => {
|
|
try {
|
|
const { influencer_id } = c.req.param();
|
|
|
|
const { data: influencer, error } = await supabase
|
|
.from('influencers')
|
|
.select(`
|
|
influencer_id,
|
|
name,
|
|
platform,
|
|
profile_url,
|
|
external_id,
|
|
followers_count,
|
|
video_count,
|
|
platform_count,
|
|
created_at,
|
|
updated_at
|
|
`)
|
|
.eq('influencer_id', influencer_id)
|
|
.single();
|
|
|
|
if (error) {
|
|
console.error('Error fetching influencer:', error);
|
|
return c.json({ error: 'Influencer not found' }, 404);
|
|
}
|
|
|
|
return c.json(influencer);
|
|
} catch (error) {
|
|
console.error('Error in getInfluencerById:', error);
|
|
return c.json({ error: 'Internal server error' }, 500);
|
|
}
|
|
};
|
|
|
|
// Get aggregated stats for influencers
|
|
const getInfluencerStats = async (c: Context) => {
|
|
try {
|
|
const { platform } = c.req.query();
|
|
|
|
let query = supabase
|
|
.from('influencers')
|
|
.select('platform, followers_count, video_count');
|
|
|
|
if (platform) {
|
|
query = query.eq('platform', platform);
|
|
}
|
|
|
|
const { data: stats, error } = await query;
|
|
|
|
if (error) {
|
|
console.error('Error fetching influencer stats:', error);
|
|
return c.json({ error: error.message }, 500);
|
|
}
|
|
|
|
const aggregatedStats = {
|
|
total_influencers: stats.length,
|
|
total_followers: stats.reduce((sum: number, item: any) => sum + (item.followers_count || 0), 0),
|
|
total_videos: stats.reduce((sum: number, item: any) => sum + (item.video_count || 0), 0),
|
|
average_followers: Math.round(
|
|
stats.reduce((sum: number, item: any) => sum + (item.followers_count || 0), 0) / (stats.length || 1)
|
|
),
|
|
average_videos: Math.round(
|
|
stats.reduce((sum: number, item: any) => sum + (item.video_count || 0), 0) / (stats.length || 1)
|
|
),
|
|
platform_distribution: stats.reduce((acc: Record<string, number>, item: any) => {
|
|
const platform = item.platform || 'unknown';
|
|
acc[platform] = (acc[platform] || 0) + 1;
|
|
return acc;
|
|
}, {})
|
|
};
|
|
|
|
return c.json(aggregatedStats);
|
|
} catch (error) {
|
|
console.error('Error in getInfluencerStats:', error);
|
|
return c.json({ error: 'Internal server error' }, 500);
|
|
}
|
|
};
|
|
|
|
// Create a new influencer
|
|
const createInfluencer = async (c: Context) => {
|
|
try {
|
|
const { name, platform, profile_url, external_id, followers_count, video_count } = await c.req.json();
|
|
|
|
if (!name) {
|
|
return c.json({ error: 'Name is required' }, 400);
|
|
}
|
|
|
|
const { data: influencer, error } = await supabase
|
|
.from('influencers')
|
|
.insert({
|
|
name,
|
|
platform,
|
|
profile_url,
|
|
external_id,
|
|
followers_count: followers_count || 0,
|
|
video_count: video_count || 0
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) {
|
|
if (error.code === '23505') { // Unique constraint violation
|
|
return c.json({ error: 'An influencer with this external ID already exists' }, 409);
|
|
}
|
|
console.error('Error creating influencer:', error);
|
|
return c.json({ error: 'Failed to create influencer' }, 500);
|
|
}
|
|
|
|
return c.json(influencer, 201);
|
|
} catch (error) {
|
|
console.error('Error in createInfluencer:', error);
|
|
return c.json({ error: 'Internal server error' }, 500);
|
|
}
|
|
};
|
|
|
|
// Update an existing influencer
|
|
const updateInfluencer = async (c: Context) => {
|
|
try {
|
|
const { influencer_id } = c.req.param();
|
|
const { name, platform, profile_url, external_id, followers_count, video_count } = await c.req.json();
|
|
|
|
// Check if influencer exists
|
|
const { data: existingInfluencer, error: fetchError } = await supabase
|
|
.from('influencers')
|
|
.select('influencer_id')
|
|
.eq('influencer_id', influencer_id)
|
|
.single();
|
|
|
|
if (fetchError || !existingInfluencer) {
|
|
return c.json({ error: 'Influencer not found' }, 404);
|
|
}
|
|
|
|
const updateData: any = {};
|
|
if (name !== undefined) updateData.name = name;
|
|
if (platform !== undefined) updateData.platform = platform;
|
|
if (profile_url !== undefined) updateData.profile_url = profile_url;
|
|
if (external_id !== undefined) updateData.external_id = external_id;
|
|
if (followers_count !== undefined) updateData.followers_count = followers_count;
|
|
if (video_count !== undefined) updateData.video_count = video_count;
|
|
updateData.updated_at = new Date().toISOString();
|
|
|
|
const { data: updatedInfluencer, error: updateError } = await supabase
|
|
.from('influencers')
|
|
.update(updateData)
|
|
.eq('influencer_id', influencer_id)
|
|
.select()
|
|
.single();
|
|
|
|
if (updateError) {
|
|
if (updateError.code === '23505') { // Unique constraint violation
|
|
return c.json({ error: 'An influencer with this external ID already exists' }, 409);
|
|
}
|
|
console.error('Error updating influencer:', updateError);
|
|
return c.json({ error: 'Failed to update influencer' }, 500);
|
|
}
|
|
|
|
return c.json(updatedInfluencer);
|
|
} catch (error) {
|
|
console.error('Error in updateInfluencer:', error);
|
|
return c.json({ error: 'Internal server error' }, 500);
|
|
}
|
|
};
|
|
|
|
// Delete an influencer
|
|
const deleteInfluencer = async (c: Context) => {
|
|
try {
|
|
const { influencer_id } = c.req.param();
|
|
|
|
// Check if influencer exists
|
|
const { data: existingInfluencer, error: fetchError } = await supabase
|
|
.from('influencers')
|
|
.select('influencer_id')
|
|
.eq('influencer_id', influencer_id)
|
|
.single();
|
|
|
|
if (fetchError || !existingInfluencer) {
|
|
return c.json({ error: 'Influencer not found' }, 404);
|
|
}
|
|
|
|
// Check if influencer has any posts
|
|
const { data: posts, error: postsError } = await supabase
|
|
.from('posts')
|
|
.select('post_id')
|
|
.eq('influencer_id', influencer_id)
|
|
.limit(1);
|
|
|
|
if (!postsError && posts && posts.length > 0) {
|
|
return c.json({
|
|
error: 'Cannot delete influencer with existing posts. Delete their posts first or use the force parameter.'
|
|
}, 400);
|
|
}
|
|
|
|
// Delete the influencer
|
|
const { error: deleteError } = await supabase
|
|
.from('influencers')
|
|
.delete()
|
|
.eq('influencer_id', influencer_id);
|
|
|
|
if (deleteError) {
|
|
console.error('Error deleting influencer:', deleteError);
|
|
return c.json({ error: 'Failed to delete influencer' }, 500);
|
|
}
|
|
|
|
return c.body(null, 204);
|
|
} catch (error) {
|
|
console.error('Error in deleteInfluencer:', error);
|
|
return c.json({ error: 'Internal server error' }, 500);
|
|
}
|
|
};
|
|
|
|
// Get all posts for a specific influencer
|
|
const getInfluencerPosts = async (c: Context) => {
|
|
try {
|
|
const { influencer_id } = c.req.param();
|
|
const { limit = '20', offset = '0' } = c.req.query();
|
|
|
|
// Check if influencer exists
|
|
const { data: influencer, error: influencerError } = await supabase
|
|
.from('influencers')
|
|
.select('influencer_id, name')
|
|
.eq('influencer_id', influencer_id)
|
|
.single();
|
|
|
|
if (influencerError || !influencer) {
|
|
return c.json({ error: 'Influencer not found' }, 404);
|
|
}
|
|
|
|
// Get posts for the influencer
|
|
const { data: posts, error: postsError, count } = await supabase
|
|
.from('posts')
|
|
.select(`
|
|
post_id,
|
|
platform,
|
|
post_url,
|
|
title,
|
|
description,
|
|
published_at,
|
|
created_at,
|
|
updated_at
|
|
`, { count: 'exact' })
|
|
.eq('influencer_id', influencer_id)
|
|
.order('published_at', { ascending: false })
|
|
.range(parseInt(offset), parseInt(offset) + parseInt(limit) - 1);
|
|
|
|
if (postsError) {
|
|
console.error('Error fetching influencer posts:', postsError);
|
|
return c.json({ error: 'Failed to fetch influencer posts' }, 500);
|
|
}
|
|
|
|
return c.json({
|
|
influencer_id,
|
|
influencer_name: influencer.name,
|
|
posts,
|
|
count,
|
|
limit: parseInt(limit),
|
|
offset: parseInt(offset)
|
|
});
|
|
} catch (error) {
|
|
console.error('Error in getInfluencerPosts:', error);
|
|
return c.json({ error: 'Internal server error' }, 500);
|
|
}
|
|
};
|
|
|
|
export default {
|
|
getInfluencers,
|
|
getInfluencerById,
|
|
getInfluencerStats,
|
|
createInfluencer,
|
|
updateInfluencer,
|
|
deleteInfluencer,
|
|
getInfluencerPosts
|
|
};
|