尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
פלטפורמת SaaS לצלמים: אתר תדמית ציבורי לכל צלם/סטודיו (דף נחיתה בכתובת /[slug]), ניהול גלריות תמונות ללקוחות (ציבוריות ופרטיות עם הגנת סיסמה), תיק עבודות (פורטפוליו), בלוג, "לפני/אחרי", חבילות שירות, המלצות לקוחות, מותג אישי (לוגו/צבעים/פונטים), ומערכת מנויים בתשלום. נבנה עם Next.js 15 (App Router, React 19), Supabase (Postgres + Auth + RLS), ואחסון מדיה ב-Cloudflare R2. ממשק המשתמש בעברית ו-RTL לאורך כל האתר.檢視完整 README ↗
פלטפורמת SaaS לצלמים: אתר תדמית ציבורי לכל צלם/סטודיו (דף נחיתה בכתובת /[slug]), ניהול גלריות תמונות ללקוחות (ציבוריות ופרטיות עם הגנת סיסמה), תיק עבודות (פורטפוליו), בלוג, "לפני/אחרי", חבילות שירות, המלצות לקוחות, מותג אישי (לוגו/צבעים/פונטים), ומערכת מנויים בתשלום.
נבנה עם Next.js 15 (App Router, React 19), Supabase (Postgres + Auth + RLS), ואחסון מדיה ב-Cloudflare R2. ממשק המשתמש בעברית ו-RTL לאורך כל האתר.
כל צלם/סטודיו מקבל:
studio-galleries.com/<slug> הכולל: עמוד בית עם תבנית עיצוב לבחירה (Classic / Modern / Elegant / Dark), תיק עבודות (/portfolio), בלוג (/blog), עמודי "לפני ואחרי" (/before-after), חבילות שירות, המלצות, טופס יצירת קשר ומפת SEO (/seo-map)./dashboard) לניהול גלריות, לקוחות, חבילות, פוסטים, המלצות, מותג (branding), הגדרות, ומנוי./[slug]/portfolio וכו') או פרטיות בכתובת מבודדת לחלוטין תחת סאב-דומיין נפרד (/g/[id]), עם הגנת סיסמה, כדי לעמוד בהתחייבות מול ספק סינון תוכן שגלריות פרטיות "חיות" רק שם ולא מוגשות מהדומיין הראשי./manage) לצוות התפעול: שידורי הודעות, ניהול תוכניות מחיר, ניקוי קבצים יתומים, חיפוש משתמשים ועוד.| שכבה | טכנולוגיה |
|---|---|
| Framework | Next.js 15.3 (App Router, Server Actions, Server Components) |
| UI | React 19, Tailwind CSS v4, shadcn/ui ("new-york" style), Radix UI, lucide-react |
| מסד נתונים / Auth | Supabase (Postgres, Row Level Security, Supabase Auth, @supabase/ssr) |
| אחסון מדיה | Cloudflare R2 (S3-compatible, דרך @aws-sdk/client-s3) |
| תשלומים | שכבת PaymentService ניטרלית-ספק, מתאמים ל-PayMe ו-SUMIT |
| דוא"ל | Resend |
| עיבוד תמונות | sharp (שרת), browser-image-compression (לקוח) |
| וידאו | mp4box (בדיקות/מטא-דאטה לווידאו הירו) |
| גרירה ושחרור | @dnd-kit (סידור תמונות/גלריות) |
| ולידציה | Zod |
| בדיקות | node --test / tsx --test, סקריפטים ייעודיים תחת scripts/ |
| Deploy | Vercel (כולל Vercel Cron) |
עקרונות מפתח:
lib/actions, Route Handlers תחת app/api), עם הרשאות Supabase (Service Role) המוגבלות למודולי שרת בלבד.middleware.ts אוכף שדומיין הגלריה הפרטית (NEXT_PUBLIC_PRIVATE_GALLERY_URL) מגיש רק את הנתיבים /g/*, /api/gallery-media, ו-/_next/* — כל דבר אחר מקבל 404, וקישורים ישנים לגלריה מהדומיין הראשי מבצעים redirect 308 לדומיין המבודד.provider_not_configured) עד שפרטי החוזה הרשמי מול הספק מאומתים; שום הפעלת מנוי לא קורית בלי webhook מאומת. ראו docs/payments-architecture.md.PAYMENTS_CHECKOUT_ENABLED, TRIAL_ENDING_REMINDERS_ENABLED, ENFORCE_SUBSCRIPTION_ACCESS נדלקים רק במחרוזת המדויקת "true".app/
├── (auth)/ # התחברות, הרשמה, שכחתי סיסמה, איפוס סיסמה
├── [slug]/ # אתר התדמית הציבורי של כל צלם (עמוד בית, פורטפוליו, בלוג, לפני/אחרי, seo-map)
├── g/[id]/ # גלריית לקוח פרטית — מוגשת מדומיין מבודד בלבד
├── dashboard/ # פאנל ניהול הצלם: גלריות, לקוחות, חבילות, פוסטים, המלצות, מותג, הגדרות, מנוי
├── manage/ # פאנל ניהול פנימי לצוות (admin)
├── portfolio/ # דפי תיק עבודות ציבוריים
├── public-gallery/ # רכיבי תמיכה לגלריה ציבורית
├── accessibility/ # הצהרת נגישות
├── privacy/ terms/ # מסמכים משפטיים
├── api/ # Route Handlers (תשלומים, מדיה, אדמין, cron, testimonials וכו')
├── layout.tsx, page.tsx # שורש האפליקציה, עמוד הבית
├── sitemap.ts, robots.ts # SEO
└── icon.tsx, apple-icon.tsx
lib/
├── actions/ # Server Actions (auth ועוד)
├── payments/ # שכבת תשלומים ניטרלית-ספק + providers/payme, providers/sumit
├── subscriptions/ # entitlements, guard, gallery-gate — מי רשאי לגשת למה
├── trial/ # תזכורות/התראות תקופת ניסיון
├── r2/ # קליינט, קונפיגורציה ומפתחות ל-Cloudflare R2
├── supabase/ # קליינטים (client/server/admin/middleware)
├── homepage-themes/ # 4 תבניות עיצוב לעמוד הבית (classic, modern, elegant, dark)
├── mappers/, queries/, validations/, types/ # שכבת גישה לנתונים וטיפוסים
├── gallery-*.ts # הרשאות, סיסמאות, סשן, מגבלות תמונות, בחירת תמונות
├── media-upload-pipeline.ts, hero-video*.ts # צנרת העלאת מדיה ווידאו הירו
├── seo/ # מטא-דאטה ציבורי ל-SEO
├── email/ # שליחת מיילים (Resend)
├── cron/ # הרשאת קריאות cron
├── referral/ # מנגנון הפניות
└── site-access/, site-gate # שערי גישה לאתר
components/
├── admin/ # פאנל ניהול פנימי
├── auth/ # טפסי התחברות/הרשמה
├── dashboard/ # רכיבי דשבורד הצלם
├── gallery/ # רכיבי גלריה (העלאה, סידור, הורדה כ-ZIP וכו')
├── marketing/ # עמוד השיווק/נחיתה של המוצר עצמו
├── photographer/ # PhotographerHomepage — הרכבת עמוד הבית הציבורי
├── public-site/ # רכיבי האתר הציבורי (פורטפוליו, בלוג וכו')
├── seo/ # רכיבי מטא/structured data
├── site-gate/ # שערי הגנה (סיסמה וכו')
└── ui/ # shadcn/ui primitives
supabase/
├── migrations/ # מיגרציות SQL (75+), הסכמה המלאה נבנית מהן
├── sql/ # שאילתות/פונקציות עזר
├── seed.sql
└── config.toml
scripts/ # סקריפטי CLI: בדיקות, אבחון, exploit/regression runners, ניהול auth redirect URLs
docs/ # תיעוד ארכיטקטורה (payments-architecture.md)
/[slug])עמוד הבית נבנה מ-4 תבניות עיצוב חלופיות (lib/homepage-themes/{classic,modern,elegant,dark}.ts), מורכב על ידי components/photographer/PhotographerHomepage.tsx. כולל הירו (תמונה/וידאו), חבילות שירות, גלריית תמונות אחרונות, פוסטים, המלצות, טופס יצירת קשר, ומיתוג אישי (lib/branding-*).
lib/public-gallery-html.ts)./g/[id]) — מוגנות סיסמה (lib/gallery-password*.ts), עם סשן ייעודי (lib/gallery-session.ts), ומוגשות אך ורק מדומיין מבודד (NEXT_PUBLIC_PRIVATE_GALLERY_URL) כפי שנאכף ב-middleware.ts. תמיכה בהורדת אלבום כ-ZIP בצד לקוח (lib/client-zip-download.ts) וסידור/בחירת תמונות (gallery-selection.ts, @dnd-kit).lib/media-upload-pipeline.ts עם דחיסה בצד לקוח (browser-image-compression) ועיבוד בצד שרת (sharp).lib/r2/), עם proxy ל-/api/gallery-media עבור מדיה בגלריות פרטיות (כדי לא לחשוף קישורי R2 ישירים).hero-video-constraints.ts), זמינות (hero-video-availability.ts) והעלאה (hero-video-upload.ts, hero-video.server.ts).שכבת lib/payments/ ניטרלית-ספק:
PaymentService — נקודת הכניסה היחידה מה-UI/Server Actions; אף קוד לקוח לא קורא ישירות לספק תשלומים.provider-factory.ts בוחר מתאם לפי PAYMENT_PROVIDER (providers/payme, providers/sumit).payment_webhook_events משמש כתיבת "inbox" אטומית (claim_payment_webhook_event) שמבטיחה עיבוד יחיד לכל אירוע, כולל טיפול ב-duplicate delivery.lib/subscriptions/ — entitlements, guard, gallery-gate קובעים מי רשאי לגשת למה, על בסיס תקופת ניסיון (trial) ומצב מנוי.lib/trial/ — תזכורות סיום תקופת ניסיון ("עוד 3 ימים") והתראות פקיעה, נשלחות ב-cron.מסמך הארכיטקטורה המלא נמצא ב-docs/payments-architecture.md — כולל דיאגרמות רצף ל-checkout ול-webhook, טבלת "מה עדיין לא ידוע" מול PayMe, ורשימת TODO מלאה לפני הפעלת קריאות אמת.
/manage)מוגן באימייל בודד (FEEDBACK_EMAIL, ראו lib/feedback-email.ts), כולל שידור הודעות לצלמים, ניהול תוכניות מחיר (PlanPricingManager), ניקוי כיסויי גלריה/תמונות מקור יתומות, וחיפוש/בירור משתמשים לפי אימייל.
supabase/migrations/, בסדר כרונולוגי (מ-20250614... ואילך) — הסכמה המלאה נבנית ע"י הרצתן ברצף.20250614000002_rls_policies.sql ומתעדכנת במיגרציות מאוחרות יותר.billing_customers, subscription_plans, subscriptions, payment_transactions, payment_webhook_events — כתיבה רק דרך Service Role; שדות רגישים (provider_metadata, raw_metadata, payloads של webhook) לא נגישים ללקוח.lib/supabase/: client.ts (דפדפן, anon key), server.ts (שרת, per-request עם קוקיז), admin.ts (Service Role — שרת בלבד).צרו קובץ .env.local בשורש הפרויקט. המשתנים המסומנים ⚠️ נדרשים בפרודקשן (נבדק אוטומטית לפני build ע"י scripts/check-required-env.mjs).
| משתנה | תיאור |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | כתובת פרויקט Supabase |
NEXT_PUBLIC_SUPABASE_ANON_KEY | מפתח anon (לקוח) |
SUPABASE_SERVICE_ROLE_KEY | מפתח service role — שרת בלבד, אף פעם לא בצד לקוח |
SUPABASE_ACCESS_TOKEN | לשימוש בסקריפט supabase:auth-urls בלבד |
| משתנה | תיאור |
|---|---|
R2_ACCESS_KEY_ID | מפתח גישה |
R2_SECRET_ACCESS_KEY | סוד גישה |
R2_ENDPOINT | כתובת ה-endpoint של הבאקט |
R2_BUCKET_NAME | שם הבאקט |
NEXT_PUBLIC_R2_PUBLIC_URL / R2_PUBLIC_URL / NEXT_PUBLIC_R2_BASE_URL | כתובת ציבורית להגשת מדיה (הראשון שמוגדר מנצח) |
| משתנה | תיאור |
|---|---|
NEXT_PUBLIC_APP_URL ⚠️ | כתובת הבסיס של האפליקציה (משמש ב-emails, sitemap, robots, redirects) |
NEXT_PUBLIC_PRIVATE_GALLERY_URL | דומיין מבודד להגשת גלריות פרטיות (/g/* בלבד) — ראו middleware.ts |
GALLERY_SESSION_SECRET | סוד לחתימת סשן גישה לגלריה פרטית |
FEEDBACK_EMAIL ⚠️ | האימייל היחיד המורשה להתחבר ל-/manage; חובה בפרודקשן |
ADMIN_NAME | שם המוצג בהתראות פנימיות |
CRON_SECRET | מאמת קריאות ל-/api/cron/* (Vercel Cron) |
MVP_BYPASS_USER_ID | מזהה משתמש (או כמה, מופרדים בפסיק) שרואים את הדשבורד המלא + גלריות פרטיות עוד לפני שנפתח לכולם |
PHOTO_LIMIT_TEST_USER_ID | מזהה משתמש לבדיקת מגבלות תמונות |
| משתנה | תיאור |
|---|---|
RESEND_API_KEY | מפתח API של Resend |
EMAIL_FROM | כתובת השולח |
| משתנה | תיאור |
|---|---|
VERCEL_API_TOKEN | טוקן API של Vercel |
VERCEL_PROJECT_ID | מזהה הפרויקט (prj_...) |
VERCEL_TEAM_ID | נדרש רק אם הפרויקט תחת Team |
| משתנה | תיאור |
|---|---|
PAYMENT_PROVIDER | payme או sumit — קובע איזה מתאם נטען |
PAYMENTS_CHECKOUT_ENABLED | "true" בלבד מפעיל checkout אמיתי; אחרת 503 + "זמין בקרוב" |
PAYMENTS_SMOKE_TEST_USER_ID | allowlist לבדיקת smoke בפרודקשן (משתמש בודד, תוכנית studio_monthly בלבד) |
TRIAL_ENDING_REMINDERS_ENABLED | מפעיל שליחת תזכורות סיום ניסיון בקרון |
TRIAL_EXPIRED_EMAIL_ENABLED | מפעיל שליחת הודעת פקיעת ניסיון |
ENFORCE_SUBSCRIPTION_ACCESS | מתועד בלבד כרגע — לא מחובר ל-middleware/guards בפועל |
| משתנה | תיאור |
|---|---|
PAYME_ENV | sandbox / production |
PAYME_API_BASE_URL | כתובת בסיס ל-API (נדרש התאמה מדויקת ל-host החי ב-production) |
PAYME_CLIENT_KEY | מפתח לקוח |
PAYME_SELLER_ID | מזהה מוכר |
PAYME_WEBHOOK_SECRET | סוד לאימות webhook |
| משתנה | תיאור |
|---|---|
SUMIT_API_BASE_URL | כתובת בסיס (ברירת מחדל מוגדרת בקוד, ניתנת לדריסה) |
SUMIT_COMPANY_ID | מזהה חברה |
SUMIT_API_KEY | מפתח API |
SUMIT_PUBLIC_KEY | מפתח ציבורי (אופציונלי) |
SUMIT_TEST_MODE | "true" למצב בדיקה |
SUMIT_PAYMENTSJS_ENABLED | "true" מפעיל את טופס הכרטיס בתוך האתר (מנוי מתחדש אמיתי). דורש את שני המפתחות הציבוריים למטה + בדיקת ₪29 חיה. כבוי → כפתור המסלול נופל לתשלום חד-פעמי |
NEXT_PUBLIC_SUMIT_COMPANY_ID | זהה ל-SUMIT_COMPANY_ID. ציבורי — SUMIT מדפיסים אותו בכל דף תשלום |
NEXT_PUBLIC_SUMIT_API_PUBLIC_KEY | מפתח הטוקניזציה הציבורי של PaymentsJS (= SUMIT_PUBLIC_KEY). ציבורי בכוונה |
test:payments-rls:local)| משתנה | תיאור |
|---|---|
SUPABASE_LOCAL_URL / SUPABASE_LOCAL_ANON_KEY / SUPABASE_LOCAL_SERVICE_ROLE_KEY | מודפסים ע"י supabase status -o env; הסקריפט מסרב לכתובות שאינן local |
לעולם אל תשמרו או תדפיסו מפתחות service role / סודות ספקי תשלום בלוגים, בקוד לקוח, או ב-commit.
דרישות: Node.js (תואם ל-Next.js 15 / React 19), חשבון Supabase, (אופציונלי לפיתוח מלא) Docker להרצת Supabase מקומי, חשבון Cloudflare R2.
# התקנת תלויות
npm install
# יצירת קובץ סביבה מקומי והשלמת המשתנים הרלוונטיים (ראו טבלה למעלה)
cp .env.example .env.local # אם אין .env.example — צרו .env.local ידנית
# הרצת שרת פיתוח
npm run dev
# או עם Turbopack:
npm run dev:turbo
האפליקציה עולה בכתובת http://localhost:3000.
npx supabase start
npx supabase db push --dry-run --local # בדיקה בלבד, לא מיישם
npx supabase migration up --local
npx supabase status -o env # מדפיס את שלושת המפתחות המקומיים
npm run build # מריץ אוטומטית scripts/check-required-env.mjs (prebuild)
npm run start
מתוך package.json:
| פקודה | תיאור |
|---|---|
npm run dev | שרת פיתוח |
npm run dev:turbo | שרת פיתוח עם Turbopack |
npm run build | בנייה לפרודקשן (כולל בדיקת env מקדימה) |
npm run start | הרצת build קיים |
npm run lint | ESLint (next lint) |
npm test | מריץ את חבילת הבדיקות המלאה (ראו למטה) |
npm run test:payments-rls:local | בדיקות RLS מול Supabase מקומי בלבד |
npm run test:exploit-critical | הרצת תרחישי exploit קריטיים (scripts/exploit-critical-runtime.mjs) |
npm run test:regression-critical | הרצת בדיקות רגרסיה קריטיות |
npm run supabase:auth-urls | עדכון auth redirect URLs בפרויקט Supabase (SUPABASE_ACCESS_TOKEN) |
סקריפטי CLI נוספים תחת scripts/ (לא כולם ב-package.json, מורצים ישירות עם tsx/node):
list-studios-with-galleries.mjs — רשימת סטודיואים עם גלריות.run-payments-trace.ts — מעקב אחר תהליך תשלום עבור משתמש smoke-test.test-sumit-*.ts — בדיקות ידניות מול חשבון SUMIT (חיבור, checkout אמיתי, מנוי חוזר וכו').test-check-*.ts / test-find-*.ts / test-inspect-*.ts / test-list-*.ts — כלי אבחון נקודתיים למסד הנתונים (בדיקת מנויים, favicon מיתוג, אובייקטי R2 בגלריה, אילוצי טבלה ועוד).קבצי
scripts/test-*.tsהרבים שאינם רשומים ב-package.json(כפי שמופיע ב-git status) הם כלי אבחון/פיתוח חד-פעמיים — מריצים אותם ידנית לפי צורך, לא חלק מ-npm test.
npm test מריץ ברצף:
scripts/test-ilike-escape.mjs — בריחה נכונה של תווים ב-ILIKE.scripts/test-rate-limit-concurrency.mjs — התנהגות rate-limit תחת concurrency.scripts/test-security-critical.mjs — בדיקות אבטחה קריטיות.scripts/test-hero-video-security.mjs — אבטחת העלאת וידאו הירו (Node עם --experimental-strip-types).scripts/test-payments.ts — חבילת בדיקות תשלומים מקיפה (PayMe, SUMIT, flags, guards).scripts/test-trial-ending-reminders.ts — לוגיקת תזכורות/אכיפת מנוי.בדיקות RLS מול Supabase (test:payments-rls:local) דורשות Supabase מקומי פעיל ומסרבות לפעול מול כתובת שאינה local.
next.config.ts): object-src 'none'; base-uri 'self'; frame-ancestors 'self' + X-Frame-Options, X-Content-Type-Options, Referrer-Policy. script-src/style-src לא מוגבלים בכוונה — עמוד הבית הציבורי מסתמך על <script>/<style> inline רבים וסקריפט חיצוני (cdn.tailwindcss.com) + Google Fonts; מדיניות מחמירה יותר דורשת מעבר ל-CSP מבוסס nonce (שינוי נפרד, לא בוצע עדיין).scripts/exploit-critical-runtime.mjs ו-scripts/regression-critical-runtime.mjs משמשים כבדיקות אבטחה/רגרסיה מורחבות מול סביבה אמיתית — יש להריץ בזהירות ורק מול סביבות מיועדות לכך.לביצוע סקירת אבטחה מלאה על השינויים הנוכחיים, ניתן להשתמש בסקיל /security-review.
מוגדרים ב-vercel.json, מאומתים ע"י CRON_SECRET (lib/cron/authorize.ts):
| נתיב | תזמון | תיאור |
|---|---|---|
/api/cron/cleanup-orphaned-photos | יומי (0 0 * * *) | ניקוי תמונות יתומות מ-R2/DB |
/api/cron/trial-ending-reminders | יומי (0 0 * * *) | שליחת תזכורות סיום תקופת ניסיון / חידוש מנוי |
הפרויקט בנוי לרוץ על Vercel:
vercel.json מגדיר את ה-Cron Jobs.scripts/check-required-env.mjs רץ אוטומטית לפני next build ומכשיל את ה-build אם משתנה סביבה חובה (כרגע: FEEDBACK_EMAIL) חסר בסביבת production (מזוהה לפי VERCEL_ENV/NODE_ENV).images.unoptimized: true — התמונות כבר נדחסות בצד לקוח לפני העלאה ל-R2, כך שהדפדפן טוען אותן ישירות ללא Vercel Image Optimization. דומיינים מורשים: *.supabase.co, albums.studio-galleries.com, *.r2.cloudflarestorage.com.experimental.serverActions.bodySizeLimit, middlewareClientMaxBodySize) — נדרש להעלאות מדיה.lib/actions/, לא לוגיקה עסקית ישירות ברכיבי UI.PaymentService — לעולם לא ישירות מ-UI או מ-Route Handler.server-only package אוכף זאת בחלק מהקבצים)."true" — כל ערך אחר (כולל undefined) נחשב כבוי.docs/payments-architecture.md.