490 lines
14 KiB
TypeScript
490 lines
14 KiB
TypeScript
import { supabase } from './supabase';
|
|
import { Campaign, Proposition, Participant, Vote, ParticipantWithVoteStatus, Setting, SmtpSettings } from '@/types';
|
|
import { encryptionService } from './encryption';
|
|
import { emailService } from './email';
|
|
|
|
// Services pour les campagnes
|
|
export const campaignService = {
|
|
async getAll(): Promise<Campaign[]> {
|
|
const { data, error } = await supabase
|
|
.from('campaigns')
|
|
.select('*')
|
|
.order('created_at', { ascending: false });
|
|
|
|
if (error) throw error;
|
|
return data || [];
|
|
},
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async create(campaign: any): Promise<Campaign> {
|
|
const { data, error } = await supabase
|
|
.from('campaigns')
|
|
.insert(campaign)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async update(id: string, updates: any): Promise<Campaign> {
|
|
const { data, error } = await supabase
|
|
.from('campaigns')
|
|
.update(updates)
|
|
.eq('id', id)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
async delete(id: string): Promise<void> {
|
|
console.log('Tentative de suppression de la campagne:', id);
|
|
|
|
// La suppression en cascade est gérée par la base de données
|
|
// grâce à ON DELETE CASCADE dans les contraintes de clés étrangères
|
|
const { error } = await supabase
|
|
.from('campaigns')
|
|
.delete()
|
|
.eq('id', id);
|
|
|
|
if (error) {
|
|
console.error('Erreur lors de la suppression:', error);
|
|
throw error;
|
|
}
|
|
|
|
console.log('Campagne supprimée avec succès');
|
|
},
|
|
|
|
async getStats(campaignId: string): Promise<{ propositions: number; participants: number }> {
|
|
const [propositionsResult, participantsResult] = await Promise.all([
|
|
supabase
|
|
.from('propositions')
|
|
.select('id', { count: 'exact', head: true })
|
|
.eq('campaign_id', campaignId),
|
|
supabase
|
|
.from('participants')
|
|
.select('id', { count: 'exact', head: true })
|
|
.eq('campaign_id', campaignId)
|
|
]);
|
|
|
|
if (propositionsResult.error) throw propositionsResult.error;
|
|
if (participantsResult.error) throw participantsResult.error;
|
|
|
|
return {
|
|
propositions: propositionsResult.count || 0,
|
|
participants: participantsResult.count || 0
|
|
};
|
|
}
|
|
};
|
|
|
|
// Services pour les propositions
|
|
export const propositionService = {
|
|
async getByCampaign(campaignId: string): Promise<Proposition[]> {
|
|
const { data, error } = await supabase
|
|
.from('propositions')
|
|
.select('*')
|
|
.eq('campaign_id', campaignId)
|
|
.order('created_at', { ascending: false });
|
|
|
|
if (error) throw error;
|
|
return data || [];
|
|
},
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async create(proposition: any): Promise<Proposition> {
|
|
const { data, error } = await supabase
|
|
.from('propositions')
|
|
.insert(proposition)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async update(id: string, updates: any): Promise<Proposition> {
|
|
try {
|
|
// Effectuer la mise à jour directement
|
|
const { data, error } = await supabase
|
|
.from('propositions')
|
|
.update(updates)
|
|
.eq('id', id)
|
|
.select('*')
|
|
.single();
|
|
|
|
if (error) {
|
|
console.error('Erreur Supabase lors de la mise à jour:', error);
|
|
if (error.code === 'PGRST116') {
|
|
throw new Error(`Proposition avec l'ID ${id} non trouvée`);
|
|
}
|
|
throw new Error(`Erreur lors de la mise à jour: ${error.message || 'Erreur inconnue'}`);
|
|
}
|
|
|
|
if (!data) {
|
|
throw new Error('Aucune donnée retournée après la mise à jour');
|
|
}
|
|
|
|
return data;
|
|
} catch (error: any) {
|
|
console.error('Erreur dans propositionService.update:', error);
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
async delete(id: string): Promise<void> {
|
|
const { error } = await supabase
|
|
.from('propositions')
|
|
.delete()
|
|
.eq('id', id);
|
|
|
|
if (error) throw error;
|
|
}
|
|
};
|
|
|
|
// Services pour les participants
|
|
export const participantService = {
|
|
async getByCampaign(campaignId: string): Promise<Participant[]> {
|
|
const { data, error } = await supabase
|
|
.from('participants')
|
|
.select('*')
|
|
.eq('campaign_id', campaignId)
|
|
.order('created_at', { ascending: false });
|
|
|
|
if (error) throw error;
|
|
return data || [];
|
|
},
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async create(participant: any): Promise<Participant> {
|
|
const { data, error } = await supabase
|
|
.from('participants')
|
|
.insert(participant)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async update(id: string, updates: any): Promise<Participant> {
|
|
try {
|
|
// Effectuer la mise à jour directement
|
|
const { data, error } = await supabase
|
|
.from('participants')
|
|
.update(updates)
|
|
.eq('id', id)
|
|
.select('*')
|
|
.single();
|
|
|
|
if (error) {
|
|
console.error('Erreur Supabase lors de la mise à jour du participant:', error);
|
|
if (error.code === 'PGRST116') {
|
|
throw new Error(`Participant avec l'ID ${id} non trouvé`);
|
|
}
|
|
throw new Error(`Erreur lors de la mise à jour du participant: ${error.message || 'Erreur inconnue'}`);
|
|
}
|
|
|
|
if (!data) {
|
|
throw new Error('Aucune donnée retournée après la mise à jour du participant');
|
|
}
|
|
|
|
return data;
|
|
} catch (error: any) {
|
|
console.error('Erreur dans participantService.update:', error);
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
async delete(id: string): Promise<void> {
|
|
const { error } = await supabase
|
|
.from('participants')
|
|
.delete()
|
|
.eq('id', id);
|
|
|
|
if (error) throw error;
|
|
}
|
|
};
|
|
|
|
// Services pour les votes
|
|
export const voteService = {
|
|
async getByParticipant(campaignId: string, participantId: string): Promise<Vote[]> {
|
|
const { data, error } = await supabase
|
|
.from('votes')
|
|
.select('*')
|
|
.eq('campaign_id', campaignId)
|
|
.eq('participant_id', participantId);
|
|
|
|
if (error) throw error;
|
|
return data || [];
|
|
},
|
|
|
|
async getByProposition(propositionId: string): Promise<Vote[]> {
|
|
const { data, error } = await supabase
|
|
.from('votes')
|
|
.select('*')
|
|
.eq('proposition_id', propositionId);
|
|
|
|
if (error) throw error;
|
|
return data || [];
|
|
},
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async create(vote: any): Promise<Vote> {
|
|
const { data, error } = await supabase
|
|
.from('votes')
|
|
.insert(vote)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async update(id: string, updates: any): Promise<Vote> {
|
|
const { data, error } = await supabase
|
|
.from('votes')
|
|
.update(updates)
|
|
.eq('id', id)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
async upsert(vote: { campaign_id: string; participant_id: string; proposition_id: string; amount: number }): Promise<Vote> {
|
|
const { data, error } = await supabase
|
|
.from('votes')
|
|
.upsert(vote, { onConflict: 'participant_id,proposition_id' })
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
async delete(id: string): Promise<void> {
|
|
const { error } = await supabase
|
|
.from('votes')
|
|
.delete()
|
|
.eq('id', id);
|
|
|
|
if (error) throw error;
|
|
},
|
|
|
|
async getByCampaign(campaignId: string): Promise<Vote[]> {
|
|
const { data, error } = await supabase
|
|
.from('votes')
|
|
.select('*')
|
|
.eq('campaign_id', campaignId);
|
|
|
|
if (error) throw error;
|
|
return data || [];
|
|
},
|
|
|
|
async getParticipantVoteStatus(campaignId: string): Promise<ParticipantWithVoteStatus[]> {
|
|
const { data: participants, error: participantsError } = await supabase
|
|
.from('participants')
|
|
.select('*')
|
|
.eq('campaign_id', campaignId);
|
|
|
|
if (participantsError) throw participantsError;
|
|
|
|
const { data: votes, error: votesError } = await supabase
|
|
.from('votes')
|
|
.select('*')
|
|
.eq('campaign_id', campaignId);
|
|
|
|
if (votesError) throw votesError;
|
|
|
|
return participants.map(participant => {
|
|
const participantVotes = votes.filter(vote => vote.participant_id === participant.id);
|
|
const totalVotedAmount = participantVotes.reduce((sum, vote) => sum + vote.amount, 0);
|
|
|
|
return {
|
|
...participant,
|
|
has_voted: participantVotes.length > 0,
|
|
total_voted_amount: totalVotedAmount
|
|
};
|
|
});
|
|
}
|
|
};
|
|
|
|
// Services pour les paramètres
|
|
export const settingsService = {
|
|
async getAll(): Promise<Setting[]> {
|
|
const { data, error } = await supabase
|
|
.from('settings')
|
|
.select('*')
|
|
.order('category', { ascending: true })
|
|
.order('key', { ascending: true });
|
|
|
|
if (error) throw error;
|
|
return data || [];
|
|
},
|
|
|
|
async getByCategory(category: string): Promise<Setting[]> {
|
|
const { data, error } = await supabase
|
|
.from('settings')
|
|
.select('*')
|
|
.eq('category', category)
|
|
.order('key', { ascending: true });
|
|
|
|
if (error) throw error;
|
|
return data || [];
|
|
},
|
|
|
|
async getByKey(key: string): Promise<Setting | null> {
|
|
const { data, error } = await supabase
|
|
.from('settings')
|
|
.select('*')
|
|
.eq('key', key)
|
|
.single();
|
|
|
|
if (error) {
|
|
if (error.code === 'PGRST116') return null; // No rows returned
|
|
throw error;
|
|
}
|
|
return data;
|
|
},
|
|
|
|
async getValue(key: string, defaultValue: string = ''): Promise<string> {
|
|
const setting = await this.getByKey(key);
|
|
return setting?.value || defaultValue;
|
|
},
|
|
|
|
async getBooleanValue(key: string, defaultValue: boolean = false): Promise<boolean> {
|
|
const value = await this.getValue(key, defaultValue.toString());
|
|
return value === 'true';
|
|
},
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async create(setting: any): Promise<Setting> {
|
|
const { data, error } = await supabase
|
|
.from('settings')
|
|
.insert(setting)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async update(key: string, updates: any): Promise<Setting> {
|
|
const { data, error } = await supabase
|
|
.from('settings')
|
|
.update(updates)
|
|
.eq('key', key)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
|
|
async setValue(key: string, value: string): Promise<Setting> {
|
|
const existing = await this.getByKey(key);
|
|
if (existing) {
|
|
return this.update(key, { value });
|
|
} else {
|
|
return this.create({ key, value, category: 'general' });
|
|
}
|
|
},
|
|
|
|
async setBooleanValue(key: string, value: boolean): Promise<Setting> {
|
|
return this.setValue(key, value.toString());
|
|
},
|
|
|
|
async delete(key: string): Promise<void> {
|
|
const { error } = await supabase
|
|
.from('settings')
|
|
.delete()
|
|
.eq('key', key);
|
|
|
|
if (error) throw error;
|
|
},
|
|
|
|
// Méthodes spécifiques pour les paramètres SMTP
|
|
async getSmtpSettings(): Promise<SmtpSettings> {
|
|
const smtpKeys = [
|
|
'smtp_host', 'smtp_port', 'smtp_username', 'smtp_password',
|
|
'smtp_secure', 'smtp_from_email', 'smtp_from_name'
|
|
];
|
|
|
|
const settings = await Promise.all(
|
|
smtpKeys.map(key => this.getByKey(key))
|
|
);
|
|
|
|
return {
|
|
host: settings[0]?.value || '',
|
|
port: parseInt(settings[1]?.value || '587'),
|
|
username: settings[2]?.value || '',
|
|
password: encryptionService.isEncrypted(settings[3]?.value || '')
|
|
? encryptionService.decrypt(settings[3]?.value || '')
|
|
: settings[3]?.value || '',
|
|
secure: settings[4]?.value === 'true',
|
|
from_email: settings[5]?.value || '',
|
|
from_name: settings[6]?.value || ''
|
|
};
|
|
},
|
|
|
|
async setSmtpSettings(smtpSettings: SmtpSettings): Promise<void> {
|
|
const settingsToUpdate = [
|
|
{ key: 'smtp_host', value: smtpSettings.host, category: 'email', description: 'Serveur SMTP' },
|
|
{ key: 'smtp_port', value: smtpSettings.port.toString(), category: 'email', description: 'Port SMTP' },
|
|
{ key: 'smtp_username', value: smtpSettings.username, category: 'email', description: 'Nom d\'utilisateur SMTP' },
|
|
{ key: 'smtp_password', value: encryptionService.encrypt(smtpSettings.password), category: 'email', description: 'Mot de passe SMTP (chiffré)' },
|
|
{ key: 'smtp_secure', value: smtpSettings.secure.toString(), category: 'email', description: 'Connexion sécurisée SSL/TLS' },
|
|
{ key: 'smtp_from_email', value: smtpSettings.from_email, category: 'email', description: 'Adresse email d\'expédition' },
|
|
{ key: 'smtp_from_name', value: smtpSettings.from_name, category: 'email', description: 'Nom d\'expédition' }
|
|
];
|
|
|
|
await Promise.all(
|
|
settingsToUpdate.map(setting => this.setValue(setting.key, setting.value))
|
|
);
|
|
},
|
|
|
|
async testSmtpConnection(smtpSettings: SmtpSettings): Promise<{ success: boolean; error?: string }> {
|
|
try {
|
|
// Validation basique des paramètres
|
|
if (!smtpSettings.host || !smtpSettings.port || !smtpSettings.username || !smtpSettings.password) {
|
|
return { success: false, error: 'Paramètres SMTP incomplets' };
|
|
}
|
|
|
|
if (smtpSettings.port < 1 || smtpSettings.port > 65535) {
|
|
return { success: false, error: 'Port SMTP invalide' };
|
|
}
|
|
|
|
if (!smtpSettings.from_email.includes('@')) {
|
|
return { success: false, error: 'Adresse email d\'expédition invalide' };
|
|
}
|
|
|
|
// Test de connexion via API route
|
|
return await emailService.testConnection(smtpSettings);
|
|
} catch (error) {
|
|
return { success: false, error: error instanceof Error ? error.message : 'Erreur inconnue' };
|
|
}
|
|
},
|
|
|
|
async sendTestEmail(smtpSettings: SmtpSettings, toEmail: string): Promise<{ success: boolean; error?: string; messageId?: string }> {
|
|
try {
|
|
// Validation de l'email de destination
|
|
if (!emailService.validateEmail(toEmail)) {
|
|
return { success: false, error: 'Adresse email de destination invalide' };
|
|
}
|
|
|
|
// Envoi de l'email de test via API route
|
|
return await emailService.sendTestEmail(smtpSettings, toEmail);
|
|
} catch (error) {
|
|
return { success: false, error: error instanceof Error ? error.message : 'Erreur lors de l\'envoi de l\'email de test' };
|
|
}
|
|
}
|
|
};
|