fix: reconcile approved live VPS source and operations

This commit is contained in:
2026-10-13 18:04:15 +03:00
parent 36f508df75
commit 6385d60dff
57 changed files with 2474 additions and 4630 deletions
+3 -5
View File
@@ -5,7 +5,6 @@ import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { FILES_DIR } from '@/collections/Files'
import { getViewer } from '@/lib/viewer'
import { freeEarlyAccess } from '@/lib/launch'
export const dynamic = 'force-dynamic'
@@ -27,10 +26,9 @@ export async function GET(_req: Request, ctx: { params: Promise<{ id: string }>
}
if (!doc?.filename) return NextResponse.json({ error: 'الملف غير موجود.' }, { status: 404 })
// L-02: all files open during free early access (owner curates file
// access in the admin; ALL vip files download while billing is off).
// Viewer checks apply only when billing is enabled.
if (doc.access === 'vip' && !freeEarlyAccess()) {
// No approved public-file relationship exists here. Free launch must not
// expose every private upload by ID; explicitly free files stay public.
if (doc.access === 'vip') {
const viewer = await getViewer()
if (!viewer) return NextResponse.json({ error: 'سجّل الدخول لتنزيل الملفات.' }, { status: 401 })
if (viewer.role !== 'vip' && viewer.role !== 'admin') {
+5 -57
View File
@@ -1,63 +1,11 @@
import { NextResponse } from 'next/server'
import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { sendMail } from '@/lib/mail'
export const dynamic = 'force-dynamic'
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
/* POST /api/notify — L-01 launch-notification list.
Idempotent: an existing email returns 200 { ok: true, alreadySubscribed: true }
(no duplicate row, no re-send). A fresh email is stored and a confirmation
is sent; if the mail transport is not wired the response is an honest 503
(the row is kept — no fake success). */
export async function POST(req: Request) {
let body: { email?: string } | null = null
try {
body = await req.json()
} catch {
return NextResponse.json({ error: 'صيغة الطلب غير صحيحة.' }, { status: 400 })
}
const email = (body?.email ?? '').trim().toLowerCase()
if (!EMAIL_RE.test(email)) {
return NextResponse.json({ error: 'أدخل بريداً إلكترونياً صحيحاً.' }, { status: 400 })
}
const payload = await getPayload({ config: configPromise })
const { docs } = await payload.find({
collection: 'launch-notifications',
where: { email: { equals: email } },
limit: 1,
overrideAccess: true,
})
if (docs.length > 0) {
return NextResponse.json({ ok: true, alreadySubscribed: true })
}
try {
await payload.create({
collection: 'launch-notifications',
data: { email, source: 'subscription' },
})
} catch (err) {
// Lost a unique-constraint race against a concurrent signup → idempotent OK.
const message = err instanceof Error ? err.message : ''
if (/duplicate key|unique constraint/i.test(message)) {
return NextResponse.json({ ok: true, alreadySubscribed: true })
}
console.error('launch notification create failed:', err)
return NextResponse.json({ error: 'تعذر حفظ البريد، حاول مرة أخرى.' }, { status: 500 })
}
const { sent } = await sendMail(
email,
'سنعلمك عند تفعيل العضوية — أنور بي آي',
'مرحباً بك في أنور بي آي.\n\nسجّلنا بريدك في قائمة الإشعار، وسنراسلك قبل تفعيل العضوية بأيام.\n\nخلال الوصول المبكر كل الأدلة المنشورة متاحة مجاناً، ولن يُنشأ لك اشتراك أو يُحصَّل أي مبلغ.',
// Delivery is not verified. Do not collect addresses or claim subscription.
export async function POST() {
return NextResponse.json(
{ error: 'التسجيل في إشعارات البريد متوقف حالياً. للتواصل: hello@anwarpy.ai' },
{ status: 503 },
)
if (!sent) {
return NextResponse.json({ error: 'خدمة البريد غير مربوطة بعد.' }, { status: 503 })
}
return NextResponse.json({ ok: true })
}
@@ -41,7 +41,7 @@ export async function POST(_request: Request, ctx: { params: Promise<{ slug: str
// Mirror the UI paywall: commenting on a VIP post requires entitlement.
if (post.access === 'vip' && role !== 'vip' && role !== 'admin') {
return NextResponse.json({ error: 'التعليق على هذا الدليل متاح لأعضاء العضوية.' }, { status: 403 })
return NextResponse.json({ error: 'التعليق على هذا المحتوى متاح لأعضاء العضوية.' }, { status: 403 })
}
// Naive per-user rate limit: 1 comment / 60 s, any post, counts pending.
+2 -3
View File
@@ -13,9 +13,8 @@ export async function POST() {
const priceId = process.env.STRIPE_PRICE_ID
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'
// L-01: checkout is disabled server-side during free early access —
// 503, and never a Checkout session in this mode. Paid transition =
// LAUNCH_MODE=paid + Stripe keys; this route re-enables with no code change.
// Free-launch policy is code-locked: Stripe configuration alone cannot
// create a checkout session. Re-enabling requires separate operator review.
if (!billingEnabled()) {
return NextResponse.json({ error: 'الدفع غير مفعّل خلال فترة الوصول المبكر.' }, { status: 503 })
}