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:
root
2026-06-24 07:02:03 +02:00
parent 913fa94fb9
commit cfff74e2db
81 changed files with 12132 additions and 2101 deletions
+32 -40
View File
@@ -51,52 +51,42 @@ ERROR_PATTERNS_JS = json.dumps([
def authenticate(browser):
"""Login with demo credentials and return authenticated context + page."""
"""Login via API and set token in localStorage, then return authenticated page."""
context = browser.new_context(viewport=VIEWPORTS[viewport])
page = context.new_page()
page.on("pageerror", lambda err: None)
try:
# Step 1: Go to homepage → login page
# Step 1: Call login API to get a JWT token
import urllib.request, json as jsonlib, ssl
login_url = f"{BASE_URL.rstrip('/')}/api/auth/login"
req = urllib.request.Request(
login_url,
data=jsonlib.dumps({"email": "demo@falahos.my", "password": "password123"}).encode(),
headers={
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36",
"Accept": "application/json",
},
method="POST",
)
ctx = ssl.create_default_context()
resp = urllib.request.urlopen(req, timeout=15, context=ctx)
auth_data = jsonlib.loads(resp.read().decode())
token = auth_data.get("token", "")
if not token:
fail("Login API returned no token")
return None, None
ok(f"Got API token: {token[:20]}...{token[-4:]}")
# Step 2: Navigate to any page and inject the token into localStorage
page.goto(f"{BASE_URL.rstrip('/')}/", timeout=TIMEOUT_MS, wait_until="domcontentloaded")
page.evaluate(f"localStorage.setItem('flh_token', '{token}')")
page.wait_for_timeout(500)
# Step 3: Reload to let React AuthContext pick up the token
page.goto(f"{BASE_URL.rstrip('/')}/", timeout=TIMEOUT_MS, wait_until="networkidle")
page.wait_for_timeout(1000)
# Step 2: Click "Continue with Email"
email_btn = page.locator("button:has-text('Continue with Email')")
if email_btn.count() > 0:
email_btn.click()
page.wait_for_timeout(1500)
# Step 3: Click "Sign in" to get to the login form
sign_in = page.locator("button:has-text('Sign in')")
if sign_in.count() > 0:
sign_in.click()
page.wait_for_timeout(1500)
# Step 4: Fill email
email_input = page.locator("input[type='email']")
if email_input.count() > 0:
email_input.fill("demo@falahos.my")
page.wait_for_timeout(200)
# Step 5: Fill password
pw_input = page.locator("input[type='password']")
if pw_input.count() > 0:
pw_input.fill("password123")
page.wait_for_timeout(200)
# Step 6: Submit
submit = page.locator("button[type='submit'], button:has-text('Sign In')")
if submit.count() > 0:
submit.first.click()
page.wait_for_timeout(3000)
# Wait for network idle after login
try:
page.wait_for_load_state("networkidle", timeout=10000)
except:
pass
page.wait_for_timeout(1000)
page.wait_for_timeout(2000)
# Verify auth
body_text = page.inner_text("body") or ""
@@ -108,6 +98,8 @@ def authenticate(browser):
return context, page
except Exception as e:
fail(f"Authentication failed: {e}")
import traceback
traceback.print_exc()
context.close()
return None, None