feat: implement core platform features including business directory, admin dashboard, authentication, and API infrastructure
Some checks failed
Build and Push App / build (push) Failing after 16m2s
Some checks failed
Build and Push App / build (push) Failing after 16m2s
This commit is contained in:
@@ -21,11 +21,14 @@ model User {
|
||||
phone String?
|
||||
isSuspended Boolean @default(false)
|
||||
suspensionReason String?
|
||||
countryId String?
|
||||
businesses Business?
|
||||
comments Comment[]
|
||||
conversations ConversationParticipant[]
|
||||
sentMessages Message[]
|
||||
reports MessageReport[]
|
||||
ratings Rating[]
|
||||
country Country? @relation(fields: [countryId], references: [id])
|
||||
}
|
||||
|
||||
model Business {
|
||||
@@ -42,6 +45,7 @@ model Business {
|
||||
verified Boolean @default(false)
|
||||
viewCount Int @default(0)
|
||||
rating Float @default(0.0)
|
||||
ratingCount Int @default(0)
|
||||
tags String[]
|
||||
isFeatured Boolean @default(false)
|
||||
founderName String?
|
||||
@@ -58,10 +62,41 @@ model Business {
|
||||
websiteUrl String?
|
||||
isSuspended Boolean @default(false)
|
||||
suspensionReason String?
|
||||
plan Plan @default(STARTER)
|
||||
city String?
|
||||
countryId String?
|
||||
country Country? @relation(fields: [countryId], references: [id])
|
||||
owner User @relation(fields: [ownerId], references: [id])
|
||||
comments Comment[]
|
||||
conversations Conversation[]
|
||||
offers Offer[]
|
||||
ratings Rating[]
|
||||
}
|
||||
|
||||
model Country {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
code String @unique
|
||||
flag String?
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
businesses Business[]
|
||||
users User[]
|
||||
}
|
||||
|
||||
model PricingPlan {
|
||||
id String @id @default(uuid())
|
||||
tier Plan @unique
|
||||
name String
|
||||
priceXOF String
|
||||
priceEUR String
|
||||
description String
|
||||
features String[]
|
||||
offerLimit Int @default(1)
|
||||
recommended Boolean @default(false)
|
||||
color String @default("gray")
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Offer {
|
||||
@@ -127,6 +162,31 @@ model AnalyticsEvent {
|
||||
value Float?
|
||||
metadata Json?
|
||||
createdAt DateTime @default(now())
|
||||
browser String?
|
||||
city String?
|
||||
country String?
|
||||
device String?
|
||||
ip String?
|
||||
os String?
|
||||
referrer String?
|
||||
userId String?
|
||||
}
|
||||
|
||||
model Rating {
|
||||
id String @id @default(uuid())
|
||||
value Int
|
||||
userId String
|
||||
businessId String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
comment String?
|
||||
reply String?
|
||||
replyAt DateTime?
|
||||
status RatingStatus @default(PENDING)
|
||||
business Business @relation(fields: [businessId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, businessId])
|
||||
}
|
||||
|
||||
model Conversation {
|
||||
@@ -173,6 +233,33 @@ model MessageReport {
|
||||
reporter User @relation(fields: [reporterId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model SiteSetting {
|
||||
id String @id @default("singleton")
|
||||
siteName String @default("Afrohub")
|
||||
siteSlogan String @default("La plateforme de référence pour l'entrepreneuriat africain.")
|
||||
contactEmail String @default("support@afrohub.com")
|
||||
contactPhone String? @default("+225 00 00 00 00 00")
|
||||
address String? @default("Abidjan, Côte d'Ivoire")
|
||||
facebookUrl String?
|
||||
twitterUrl String?
|
||||
instagramUrl String?
|
||||
linkedinUrl String?
|
||||
footerText String? @default("© 2025 Afrohub. Tous droits réservés.")
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
enum Plan {
|
||||
STARTER
|
||||
BOOSTER
|
||||
EMPIRE
|
||||
}
|
||||
|
||||
enum RatingStatus {
|
||||
PENDING
|
||||
APPROVED
|
||||
REJECTED
|
||||
}
|
||||
|
||||
enum ReportStatus {
|
||||
PENDING
|
||||
RESOLVED
|
||||
@@ -194,18 +281,10 @@ enum InterviewType {
|
||||
VIDEO
|
||||
ARTICLE
|
||||
}
|
||||
|
||||
model SiteSetting {
|
||||
id String @id @default("singleton")
|
||||
siteName String @default("Afropreunariat")
|
||||
siteSlogan String @default("La plateforme de référence pour l'entrepreneuriat africain.")
|
||||
contactEmail String @default("support@afropreunariat.com")
|
||||
contactPhone String? @default("+225 00 00 00 00 00")
|
||||
address String? @default("Abidjan, Côte d'Ivoire")
|
||||
facebookUrl String?
|
||||
twitterUrl String?
|
||||
instagramUrl String?
|
||||
linkedinUrl String?
|
||||
footerText String? @default("© 2025 Afropreunariat. Tous droits réservés.")
|
||||
updatedAt DateTime @updatedAt
|
||||
model LegalDocument {
|
||||
id String @id @default(uuid())
|
||||
type String @unique // 'CGU' or 'CGV'
|
||||
title String
|
||||
content String @db.Text
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
123
prisma/seed-30.ts
Normal file
123
prisma/seed-30.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
import pg from 'pg';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config({ path: '.env.local' });
|
||||
dotenv.config(); // Fallback to .env for other vars if needed
|
||||
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
console.log(`Connecting to: ${connectionString.split('@')[1] ? '***@' + connectionString.split('@')[1] : connectionString}`);
|
||||
const pool = new pg.Pool({ connectionString });
|
||||
const adapter = new PrismaPg(pool);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
const CATEGORIES = [
|
||||
'Technologie & IT', 'Restauration & Alimentation', 'Mode & Textile', 'Artisanat & Déco',
|
||||
'Santé & Bien-être', 'Éducation & Formation', 'Services aux entreprises', 'Construction & BTP',
|
||||
'Agriculture & Agrobusiness', 'Tourisme & Transport'
|
||||
];
|
||||
|
||||
const CITIES = ['Abidjan', 'Dakar', 'Lagos', 'Douala', 'Kinshasa', 'Cotonou', 'Lomé', 'Paris', 'Lyon', 'Bruxelles'];
|
||||
|
||||
const NAMES = [
|
||||
'Moussa Diarra', 'Fatou Sow', 'Bakary Koné', 'Awa Traoré', 'Koffi Adjoumani',
|
||||
'Cheick Tidiane', 'Bintu Camara', 'Ousmane Diallo', 'Yasmine Touré', 'Ibrahim Sy',
|
||||
'Mariam Keita', 'Lamine Ndiaye', 'Aminata Faye', 'Idrissa Gueye', 'Sékou Condé',
|
||||
'Zainab Bello', 'Kwame Nkrumah', 'Chidi Okafor', 'Adama Barrow', 'Fatimata Sylla',
|
||||
'Jean-Pierre Mbeki', 'Grace Akoto', 'Samuel Eto\'o', 'Didier Drogba', 'Blaise Diagne',
|
||||
'Léopold Senghor', 'Thomas Sankara', 'Miriam Makeba', 'Angélique Kidjo', 'Alpha Blondy'
|
||||
];
|
||||
|
||||
const BIZ_NAMES = [
|
||||
'Digital Africa', 'Saveurs du Sahel', 'Wax & Modernity', 'Artisans du Delta', 'Zenith Wellness',
|
||||
'Afro-EdTech', 'Bizi Solutions', 'Sahara Build', 'Agro-Innov', 'Trans-Africa Express',
|
||||
'Cyber-Sénégal', 'Miam-Miam Abidjan', 'Élégance Bamako', 'Poterie de Kati', 'Savon de Guinée',
|
||||
'Code Academy', 'Compta Facile', 'Solar Power West', 'Cocoa Direct', 'Taxi-Plus',
|
||||
'Cloud Kinshasa', 'Épices de Douala', 'Tissage d\'Accra', 'Bambou Design', 'Bio-Cosmetiques',
|
||||
'E-Learning Africa', 'Logistique 225', 'Refuge Solaire', 'Ananas Export', 'Linga-Linga Travel'
|
||||
];
|
||||
|
||||
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
async function seed() {
|
||||
console.log('--- Démarrage du seeding robuste (30 entrepreneurs) ---');
|
||||
|
||||
const hashedPassword = await bcrypt.hash('afrohub2025', 10);
|
||||
let successCount = 0;
|
||||
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const name = NAMES[i % NAMES.length] + (i > 29 ? ` ${i}` : '');
|
||||
const email = `entrepreneur${i + 1}@afrohub-test.com`;
|
||||
const bizName = BIZ_NAMES[i % BIZ_NAMES.length];
|
||||
const category = CATEGORIES[i % CATEGORIES.length];
|
||||
const location = CITIES[i % CITIES.length];
|
||||
const slug = bizName.toLowerCase().replace(/ /g, '-').replace(/[^a-z0-9-]/g, '') + `-${i + 1}`;
|
||||
|
||||
try {
|
||||
// Create or Update User
|
||||
const user = await prisma.user.upsert({
|
||||
where: { email },
|
||||
update: { role: 'ENTREPRENEUR' },
|
||||
create: {
|
||||
name,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
role: 'ENTREPRENEUR',
|
||||
location,
|
||||
avatar: `https://i.pravatar.cc/150?u=${email}`
|
||||
}
|
||||
});
|
||||
|
||||
// Create or Update Business
|
||||
await prisma.business.upsert({
|
||||
where: { ownerId: user.id },
|
||||
update: {
|
||||
isActive: true,
|
||||
verified: true,
|
||||
isFeatured: i < 8
|
||||
},
|
||||
create: {
|
||||
name: bizName,
|
||||
category,
|
||||
location,
|
||||
description: `Société leader dans le domaine ${category}. Basés à ${location}, nous œuvrons pour le rayonnement de l'expertise africaine.`,
|
||||
logoUrl: `https://picsum.photos/200/200?random=${i + 200}`,
|
||||
contactEmail: email,
|
||||
contactPhone: `+225 00 ${i}${i} ${i}${i} ${i}${i}`,
|
||||
slug,
|
||||
isActive: true,
|
||||
verified: true,
|
||||
isFeatured: i < 8,
|
||||
ownerId: user.id,
|
||||
tags: [category.split(' ')[0], 'Expertise', 'Continent'],
|
||||
viewCount: Math.floor(Math.random() * 500)
|
||||
}
|
||||
});
|
||||
|
||||
successCount++;
|
||||
process.stdout.write(`\rProgress: ${successCount}/30`);
|
||||
|
||||
await wait(100);
|
||||
} catch (e: any) {
|
||||
console.error(`\n❌ Échec pour ${email}:`);
|
||||
console.error('Message:', e.message);
|
||||
if (e.code) console.error('Code:', e.code);
|
||||
if (e.meta) console.error('Meta:', e.meta);
|
||||
await wait(1000);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n\n✅ Mission accomplie : ${successCount}/30 boutiques actives.`);
|
||||
console.log(`Identifiants : entrepreneurX@afrohub-test.com / afrohub2025`);
|
||||
|
||||
await prisma.$disconnect();
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
seed().catch(async (err) => {
|
||||
console.error("Erreur fatale lors du seeding:", err);
|
||||
await pool.end();
|
||||
process.exit(1);
|
||||
});
|
||||
97
prisma/seed-plans.ts
Normal file
97
prisma/seed-plans.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import pkg from '@prisma/client';
|
||||
const { PrismaClient } = pkg;
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
import pg from 'pg';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config({ path: '.env.local' });
|
||||
dotenv.config();
|
||||
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const pool = new pg.Pool({ connectionString });
|
||||
const adapter = new PrismaPg(pool);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
const Plan = {
|
||||
STARTER: 'STARTER',
|
||||
BOOSTER: 'BOOSTER',
|
||||
EMPIRE: 'EMPIRE'
|
||||
} as const;
|
||||
|
||||
async function main() {
|
||||
console.log('Seeding pricing plans...');
|
||||
|
||||
const plans = [
|
||||
{
|
||||
tier: Plan.STARTER,
|
||||
name: 'Starter',
|
||||
priceXOF: 'Gratuit',
|
||||
priceEUR: '0€',
|
||||
description: 'Pour démarrer votre présence en ligne.',
|
||||
features: [
|
||||
'Fiche entreprise basique',
|
||||
'Visible dans la recherche',
|
||||
'1 Offre produit/service',
|
||||
'Support par email'
|
||||
],
|
||||
offerLimit: 1,
|
||||
recommended: false,
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
tier: Plan.BOOSTER,
|
||||
name: 'Booster',
|
||||
priceXOF: '5.000 FCFA',
|
||||
priceEUR: '8€',
|
||||
description: "L'indispensable pour les entreprises en croissance.",
|
||||
features: [
|
||||
'Tout du plan Starter',
|
||||
'Badge "Vérifié" ✅',
|
||||
'Jusqu\'à 10 Offres produits',
|
||||
'Lien vers réseaux sociaux & Site Web',
|
||||
'Statistiques de base (Vues)'
|
||||
],
|
||||
offerLimit: 10,
|
||||
recommended: true,
|
||||
color: 'brand'
|
||||
},
|
||||
{
|
||||
tier: Plan.EMPIRE,
|
||||
name: 'Empire',
|
||||
priceXOF: '15.000 FCFA',
|
||||
priceEUR: '23€',
|
||||
description: 'Dominez votre marché avec une visibilité maximale.',
|
||||
features: [
|
||||
'Tout du plan Booster',
|
||||
'Badge "Recommandé" 🏆',
|
||||
'Offres illimitées',
|
||||
'Intégration vidéo Youtube',
|
||||
'Interview écrite sur le Blog',
|
||||
'Support prioritaire WhatsApp'
|
||||
],
|
||||
offerLimit: 1000,
|
||||
recommended: false,
|
||||
color: 'gray'
|
||||
}
|
||||
];
|
||||
|
||||
for (const planData of plans) {
|
||||
await prisma.pricingPlan.upsert({
|
||||
where: { tier: planData.tier as any },
|
||||
update: planData,
|
||||
create: planData,
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Pricing plans seeded successfully.');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
await pool.end();
|
||||
});
|
||||
@@ -29,8 +29,8 @@ async function main() {
|
||||
// Create Main Admin
|
||||
const admin = await prisma.user.create({
|
||||
data: {
|
||||
name: 'Admin Afropreunariat',
|
||||
email: 'admin@afropreunariat.com',
|
||||
name: 'Admin Afrohub',
|
||||
email: 'admin@afrohub.com',
|
||||
password: hashedPassword,
|
||||
role: UserRole.ADMIN,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user