Files
mes-budgets-participatifs/src/lib/services.ts
2025-08-25 22:41:39 +02:00

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' };
}
}
};