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

This commit is contained in:
2026-04-18 22:10:19 +02:00
parent 6ec1a3ae84
commit 3e2063e4fa
89 changed files with 4405 additions and 967 deletions

View File

@@ -5,90 +5,104 @@ generator client {
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum UserRole {
VISITOR
ENTREPRENEUR
ADMIN
}
model User {
id String @id @default(uuid())
name String
email String @unique
password String
role UserRole @default(VISITOR)
avatar String?
phone String?
bio String? @db.Text
location String?
isSuspended Boolean @default(false)
suspensionReason String?
businesses Business?
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Messaging relations
sentMessages Message[]
conversations ConversationParticipant[]
reports MessageReport[]
id String @id @default(uuid())
name String
email String @unique
password String
role UserRole @default(VISITOR)
avatar String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bio String?
location String?
countryId String?
phone String?
isSuspended Boolean @default(false)
suspensionReason String?
businesses Business?
comments Comment[]
conversations ConversationParticipant[]
sentMessages Message[]
reports MessageReport[]
ratings Rating[]
country Country? @relation(fields: [countryId], references: [id])
}
model Business {
id String @id @default(uuid())
name String
slug String? @unique
category String
location String
description String @db.Text
logoUrl String
videoUrl String?
// Contact & Social
contactEmail String
contactPhone String?
websiteUrl String?
// We use JSON to store social links (facebook, linkedin, instagram, website)
socialLinks Json?
// Privacy & Status
showEmail Boolean @default(true)
showPhone Boolean @default(true)
showSocials Boolean @default(true)
isActive Boolean @default(false)
// Stats
verified Boolean @default(false)
viewCount Int @default(0)
rating Float @default(0.0)
tags String[]
isSuspended Boolean @default(false)
suspensionReason String?
// Entrepreneur of the Month fields
isFeatured Boolean @default(false)
id String @id @default(uuid())
name String
category String
location String // Legacy location string
countryId String?
city String?
description String
logoUrl String
videoUrl String?
contactEmail String
contactPhone String?
socialLinks Json?
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?
founderImageUrl String?
keyMetric String?
// Relations
ownerId String @unique
owner User @relation(fields: [ownerId], references: [id])
offers Offer[]
comments Comment[]
conversations Conversation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ownerId String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
slug String? @unique
isActive Boolean @default(false)
showEmail Boolean @default(true)
showPhone Boolean @default(true)
showSocials Boolean @default(true)
websiteUrl String?
isSuspended Boolean @default(false)
suspensionReason String?
plan Plan @default(STARTER)
owner User @relation(fields: [ownerId], references: [id])
comments Comment[]
conversations Conversation[]
offers Offer[]
ratings Rating[]
country Country? @relation(fields: [countryId], references: [id])
}
enum OfferType {
PRODUCT
SERVICE
model Country {
id String @id @default(uuid())
name String @unique
code String @unique // ISO alpha-2
flag String? // Emoji or URL
isActive Boolean @default(true)
businesses Business[]
users User[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Plan {
STARTER
BOOSTER
EMPIRE
}
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 {
@@ -96,52 +110,28 @@ model Offer {
title String
type OfferType
price Float
currency String @default("XOF") // EUR or XOF
description String? @db.Text
currency String @default("XOF")
description String?
imageUrl String
active Boolean @default(true)
// Relations
businessId String
business Business @relation(fields: [businessId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
business Business @relation(fields: [businessId], references: [id], onDelete: Cascade)
}
model BlogPost {
id String @id @default(uuid())
title String
excerpt String @db.Text
content String @db.Text
author String
excerpt String
content String
author String
date DateTime @default(now())
imageUrl String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
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 Interview {
id String @id @default(uuid())
title String
@@ -151,73 +141,97 @@ model Interview {
type InterviewType
thumbnailUrl String
videoUrl String?
content String? @db.Text
excerpt String @db.Text
content String?
excerpt String
date DateTime @default(now())
duration String? // "15 min" ou "5 min de lecture"
duration String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Comment {
id String @id @default(uuid())
content String @db.Text
// Relations
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
id String @id @default(uuid())
content String
authorId String
businessId String
business Business @relation(fields: [businessId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
business Business @relation(fields: [businessId], references: [id], onDelete: Cascade)
}
model AnalyticsEvent {
id String @id @default(uuid())
type String // PAGE_VIEW, CLICK, DWELL_TIME
type String
path String
label String? // For clicks (button text, link name)
value Float? // For dwell time (seconds)
metadata Json? // Browser, OS, etc.
label String?
value Float?
metadata Json?
ip String?
country String?
city String?
browser String?
os String?
device String?
referrer String?
userId String?
createdAt DateTime @default(now())
}
model Conversation {
model Rating {
id String @id @default(uuid())
value Int
comment String?
reply String?
replyAt DateTime?
status RatingStatus @default(PENDING)
userId String
businessId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
business Business @relation(fields: [businessId], references: [id], onDelete: Cascade)
@@unique([userId, businessId])
}
enum RatingStatus {
PENDING
APPROVED
REJECTED
}
model Conversation {
id String @id @default(uuid())
businessId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
business Business @relation(fields: [businessId], references: [id], onDelete: Cascade)
participants ConversationParticipant[]
messages Message[]
}
model ConversationParticipant {
id String @id @default(uuid())
id String @id @default(uuid())
userId String
conversationId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
isArchived Boolean @default(false)
isArchived Boolean @default(false)
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, conversationId])
}
model Message {
id String @id @default(uuid())
content String @db.Text
id String @id @default(uuid())
content String
senderId String
conversationId String
createdAt DateTime @default(now())
read Boolean @default(false)
sender User @relation(fields: [senderId], references: [id], onDelete: Cascade)
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
reports MessageReport[]
createdAt DateTime @default(now())
read Boolean @default(false)
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
sender User @relation(fields: [senderId], references: [id], onDelete: Cascade)
reports MessageReport[]
}
model MessageReport {
@@ -227,9 +241,8 @@ model MessageReport {
reporterId String
status ReportStatus @default(PENDING)
createdAt DateTime @default(now())
message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
reporter User @relation(fields: [reporterId], references: [id], onDelete: Cascade)
message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
reporter User @relation(fields: [reporterId], references: [id], onDelete: Cascade)
}
enum ReportStatus {
@@ -237,3 +250,34 @@ enum ReportStatus {
RESOLVED
DISMISSED
}
enum UserRole {
VISITOR
ENTREPRENEUR
ADMIN
}
enum OfferType {
PRODUCT
SERVICE
}
enum InterviewType {
VIDEO
ARTICLE
}
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
}