feat: Souq native integration + auth simplification + CE-wide enhancements
Souq Marketplace: - Native souq pages: browse+filters, create listing, detail+seller profile, orders+chat - New API: reviews (POST), sellers/[id], upload (multipart), polar-checkout webhook - Listings API enhanced: minPrice, maxPrice, sortBy, deliveryDays filters - Seller workflow: start order, mark delivered, upload files, confirm delivery - Wallet: 5 Polar.sh FLH tiers, production checkout, mock fallback, success banner - Fix: order redirect /souq/history → /souq/orders Auth System: - Unified auth page, removed OAuth provider routing (2 files deleted) - Deleted oauth.ts (319 lines) — simplified auth chain - Streamlined login/register API routes Prisma Schema (+179 lines): - New models: Listing, Purchase, Review, Group/GroupMember - Gamification: DailyStreak, XpTransaction, Achievement, UserLevel - Learning: LearnCourse/Module/Enrollment/Certificate - Notifications, Referrals, Dhikr, PremiumBenefits Deleted legacy code: - src/app/api/marketplace/* (3 files) — replaced by /api/souq/* - src/app/api/files/[listingId]/route.ts — replaced by /api/souq/upload - src/app/api/seller/[sellerId]/route.ts — replaced by /api/souq/sellers/[id] Infrastructure: - Dockerfile: standalone output, Prisma runtime migration - docker-compose.yml: Polar env vars, healthcheck fix - docker-compose.staging.yml: staging on port 4014 - .gitea/workflows/ci.yml: CI pipeline
This commit is contained in:
+166
-13
@@ -55,6 +55,7 @@ model User {
|
||||
userLevel UserLevel?
|
||||
dhikrSessions DhikrSession[]
|
||||
dhikrDays DhikrDay[]
|
||||
reviewListings Review[] @relation("ListingReviews")
|
||||
}
|
||||
|
||||
model DailyStreak {
|
||||
@@ -107,7 +108,15 @@ model Listing {
|
||||
title String
|
||||
description String
|
||||
category String
|
||||
subcategory String?
|
||||
priceFlh Int
|
||||
packages String? // JSON: [{name, description, price, deliveryDays, revisions}]
|
||||
tags String? // JSON: ["tag1", "tag2"]
|
||||
images String? // JSON: ["url1"]
|
||||
deliveryDays Int?
|
||||
rating Float @default(0)
|
||||
reviewCount Int @default(0)
|
||||
salesCount Int @default(0)
|
||||
sellerId String
|
||||
seller User @relation(fields: [sellerId], references: [id])
|
||||
status String @default("active")
|
||||
@@ -117,21 +126,48 @@ model Listing {
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
purchases Purchase[]
|
||||
reviews Review[]
|
||||
}
|
||||
|
||||
model Purchase {
|
||||
id String @id @default(cuid())
|
||||
listingId String
|
||||
listing Listing @relation(fields: [listingId], references: [id])
|
||||
buyerId String
|
||||
buyer User @relation("BuyerPurchases", fields: [buyerId], references: [id])
|
||||
sellerId String
|
||||
seller User @relation("SellerPurchases", fields: [sellerId], references: [id])
|
||||
amountFlh Int
|
||||
platformFee Int
|
||||
sellerPayout Int
|
||||
autoConfirmAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
id String @id @default(cuid())
|
||||
listingId String
|
||||
listing Listing @relation(fields: [listingId], references: [id])
|
||||
buyerId String
|
||||
buyer User @relation("BuyerPurchases", fields: [buyerId], references: [id])
|
||||
sellerId String
|
||||
seller User @relation("SellerPurchases", fields: [sellerId], references: [id])
|
||||
packageName String?
|
||||
amountFlh Int
|
||||
platformFee Int
|
||||
sellerPayout Int
|
||||
status String @default("pending") // pending, paid, in_progress, delivered, completed, disputed, cancelled
|
||||
messages String? // JSON: [{from, text, timestamp}]
|
||||
deliveryFiles String? // JSON: [{name, size, url}]
|
||||
autoConfirmAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
reviews Review[]
|
||||
}
|
||||
|
||||
model Review {
|
||||
id String @id @default(cuid())
|
||||
listingId String
|
||||
listing Listing @relation(fields: [listingId], references: [id])
|
||||
purchaseId String
|
||||
purchase Purchase @relation(fields: [purchaseId], references: [id])
|
||||
reviewerId String
|
||||
reviewer User @relation("ListingReviews", fields: [reviewerId], references: [id])
|
||||
sellerId String
|
||||
rating Int
|
||||
title String?
|
||||
text String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([listingId])
|
||||
@@index([sellerId])
|
||||
}
|
||||
|
||||
model CashoutRequest {
|
||||
@@ -333,6 +369,123 @@ model DhikrDay {
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
// Add Relations to User model
|
||||
// ──────────────────────────────────────
|
||||
// LEARN / MICRO LEARNING MODULE
|
||||
// ──────────────────────────────────────
|
||||
|
||||
model LearnCourse {
|
||||
id String @id @default(cuid())
|
||||
slug String @unique
|
||||
title String
|
||||
author String
|
||||
description String
|
||||
imageUrl String?
|
||||
moduleCount Int @default(0)
|
||||
totalMinutes Int @default(0)
|
||||
difficulty String @default("beginner")
|
||||
giteaPath String // path in Gitea courses repo
|
||||
priceFlh Int? // FLH one-time price
|
||||
priceUsd Float? // USD one-time price via Polar
|
||||
subscriptionOnly Boolean @default(false)
|
||||
published Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
modules LearnModule[]
|
||||
enrollments LearnEnrollment[]
|
||||
certificates LearnCertificate[]
|
||||
}
|
||||
|
||||
model LearnModule {
|
||||
id String @id @default(cuid())
|
||||
courseId String
|
||||
course LearnCourse @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
||||
order Int
|
||||
title String
|
||||
slug String
|
||||
keyTakeaway String?
|
||||
duration Int @default(5) // minutes
|
||||
content String? // cached markdown body
|
||||
audioPath String? // cached TTS audio file path
|
||||
quizData String? // JSON: [{question, options[], correctIndex}]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
progress LearnModuleProgress[]
|
||||
|
||||
@@unique([courseId, slug])
|
||||
}
|
||||
|
||||
model LearnEnrollment {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
courseId String
|
||||
course LearnCourse @relation(fields: [courseId], references: [id])
|
||||
purchaseType String @default("one_time") // "one_time" | "subscription" | "free"
|
||||
completed Boolean @default(false)
|
||||
progress Float @default(0) // 0.0 to 1.0
|
||||
startedAt DateTime @default(now())
|
||||
completedAt DateTime?
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
modules LearnModuleProgress[]
|
||||
|
||||
@@unique([userId, courseId])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model LearnModuleProgress {
|
||||
id String @id @default(cuid())
|
||||
enrollmentId String
|
||||
enrollment LearnEnrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade)
|
||||
moduleId String
|
||||
module LearnModule @relation(fields: [moduleId], references: [id], onDelete: Cascade)
|
||||
completed Boolean @default(false)
|
||||
score Float? // quiz score 0-100
|
||||
listened Boolean @default(false)
|
||||
completedAt DateTime?
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([enrollmentId, moduleId])
|
||||
}
|
||||
|
||||
model LearnSubscription {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
status String @default("active") // active | cancelled | expired
|
||||
polarSubId String? @unique
|
||||
currentPeriodStart DateTime
|
||||
currentPeriodEnd DateTime
|
||||
cancelledAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId])
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
model LearnCertificate {
|
||||
id String @id @default(cuid())
|
||||
serialNumber String @unique
|
||||
userId String
|
||||
userName String // cached at issue time
|
||||
courseId String
|
||||
course LearnCourse @relation(fields: [courseId], references: [id])
|
||||
moduleCount Int @default(0)
|
||||
score Float? // aggregate quiz score
|
||||
pdfPath String? // /data/certificates/{serial}.pdf
|
||||
issuedAt DateTime @default(now())
|
||||
viewedAt DateTime?
|
||||
revoked Boolean @default(false)
|
||||
revokedAt DateTime?
|
||||
hashOnChain String? // SHA256 for future blockchain anchoring
|
||||
metadata String? // JSON extra
|
||||
|
||||
@@index([serialNumber])
|
||||
@@index([userId])
|
||||
@@index([courseId, userId])
|
||||
}
|
||||
|
||||
// Add relations to User model
|
||||
/// NOTE: The Notification and Referral relations are declared on the models above.
|
||||
/// User now implicitly has: notifications Notification[], referredReferrals Referral[] (via "Referrer"), referrerReferral Referral? (via "Referred")
|
||||
|
||||
Reference in New Issue
Block a user