Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Düğün salonları ve organizasyon mekanları için multi-tenant SaaS yönetim paneli. Rezervasyon, takvim, müşteri, tahsilat, gider ve kârlılık takibi tek panelde. Stack: Next.js 16 (App Router) · TypeScript · Tailwind CSS v4 · shadcn/ui · Supabase (PostgreSQL + Auth) supabase.com üzerinde yeni bir proje açın, ardından SQL Editor'de supabase/migrations/ altındaki dosyaları sırasıyla çalıştırın: Zaten kurulu bir veritabanına yeni migration eklerken yalnızca eksik olan dosyaları sırasıyla çalıştırın; hepsi eklemeli (additive) yazılmıştır.README 전문 보기 ↗
Düğün salonları ve organizasyon mekanları için multi-tenant SaaS yönetim paneli. Rezervasyon, takvim, müşteri, tahsilat, gider ve kârlılık takibi tek panelde.
Stack: Next.js 16 (App Router) · TypeScript · Tailwind CSS v4 · shadcn/ui · Supabase (PostgreSQL + Auth)
supabase.com üzerinde yeni bir proje açın, ardından
SQL Editor'de supabase/migrations/ altındaki dosyaları sırasıyla çalıştırın:
0001_init.sql → extension'lar, enum'lar, tablolar, kısıtlar
0002_functions.sql → yardımcı fonksiyonlar, trigger'lar, kolon varsayılanları
0003_rls.sql → Row Level Security politikaları
0004_views_rpc.sql → view'lar, RPC'ler, izinler
0005_pricing_type.sql → kişi başı fiyatlandırma
0006_audit_fixes.sql → fiyat satırı garantisi, müşteri bakiyesi düzeltmesi
Zaten kurulu bir veritabanına yeni migration eklerken yalnızca eksik olan dosyaları sırasıyla çalıştırın; hepsi eklemeli (additive) yazılmıştır.
Supabase CLI kullanıyorsanız
supabase db pushde aynı işi yapar.
.env.example dosyasını .env.local olarak kopyalayın ve doldurun:
| Değişken | Nereden alınır | Zorunlu |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Project Settings → API → Project URL | Evet |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Project Settings → API → anon public | Evet |
SUPABASE_SERVICE_ROLE_KEY | Project Settings → API → service_role | Personel daveti için |
NEXT_PUBLIC_APP_URL | Uygulamanın adresi | Hayır (varsayılan localhost) |
SUPABASE_SERVICE_ROLE_KEY asla istemciye sızmamalıdır; yalnızca
src/lib/supabase/admin.ts içinden, çağıranın yöneticiliği doğrulandıktan sonra
kullanılır.
npm install && npm run dev
İlk kullanıcı /kayit adresinden işletmesini oluşturur ve otomatik olarak
İşletme Sahibi rolünü alır. E-posta doğrulaması açıksa kullanıcı, doğrulama
sonrası /isletme-kur adımında işletmesini tamamlar.
npm run dev # geliştirme sunucusu
npm run build # üretim derlemesi (TypeScript kontrolü dahil)
npm run lint # ESLint
npm run test:db # veritabanı şeması + RLS testleri (PGlite, Docker gerekmez)
npm run test:schemas # zod şemaları (idempotentlik dahil)
npm run test:audit # saldırgan denetim senaryoları (tenant sızıntısı, finans tutarsızlığı)
Boş bir panelde dashboard ve raporları değerlendirmek zor. Son 1 ay için gerçekçi bir veri seti üretmek üzere:
node supabase/seed/demo.mjs # ekle (tekrar çalıştırmak veriyi çoğaltmaz)
node supabase/seed/demo.mjs --temizle # yalnızca demo kayıtlarını sil
Demo kayıtları dddddddd-dddd-4ddd-8ddd-… önekli sabit UUID'lerle yazılır, bu
yüzden gerçek kayıtlarınıza dokunmadan eksiksiz silinebilir.
SUPABASE_SERVICE_ROLE_KEY gerekir.
npm run test:db migration'ları gerçek bir PostgreSQL üzerinde (WASM) çalıştırır
ve tenant izolasyonu, çakışma kontrolü, finansal değişmezlik, rol bazlı finans
kısıtı gibi davranışları doğrular. Şemayı her değiştirdiğinizde çalıştırın.
Her tabloda business_id var, RLS enable + force durumda ve politikalar
current_business_id() (security definer) fonksiyonuna dayanıyor. İzolasyon
frontend filtrelerine bırakılmadı: sorgular business_id göndermese bile
kolon varsayılanı + WITH CHECK yanlış tenant'a yazmayı engelliyor. Ayrıca
tüm ilişkiler bileşik yabancı anahtar (id, business_id) üzerinden kurulu —
bir tenant'ın kaydı başka bir tenant'ın kaydına bağlanamıyor.
Bu ayrım şemaya kadar işlenmiş durumda:
reservation_financials).finance_summary() ikisini ayrı kolonlarda döndürür; arayüz de ayrı başlıklar
altında gösterir.payments ve expenses kayıtları silinemez (DELETE yetkisi geri alınmış)
ve tutarı/bağlantısı değiştirilemez (trigger engeller). Hata olduğunda kayıt
voided_at + void_reason ile iptal edilip yenisi açılır. Böylece "kalan tutar"
ve geçmiş raporlar geriye dönük olarak bozulmaz, tam denetim izi kalır.
Ek koruma: tahsilat toplamı net satışı aşamaz, net satış da tahsil edilenin altına indirilemez (her ikisi de trigger ile).
reservations üzerindeki EXCLUDE USING gist kısıtı, aynı salonda çakışan
tarih/saat aralığını veritabanı seviyesinde engeller (iptal edilenler hariç).
Arayüz kullanıcıyı ayrıca uyarır, ama gerçek garanti buradan gelir. Gece yarısını
aşan organizasyonlar (20:00 – 02:00) generated kolonlarla doğru hesaplanır.
Personelin finansal verileri görmemesi bir arayüz kararı değil: fiyat bilgisi
reservations tablosunda değil, ayrı bir reservation_pricing tablosunda tutulur
ve payments / expenses ile birlikte can_see_finance() kapısının arkasındadır.
Yetkisiz personel doğrudan sorgu atsa bile tutarları göremez.
Gün ve ay sınırları sunucunun yereline değil işletmenin saat dilimine göre
hesaplanır (src/lib/time.ts, varsayılan Europe/Istanbul,
NEXT_PUBLIC_APP_TIME_ZONE ile değiştirilebilir). Sunucu UTC'de çalıştığında
Türkiye saatiyle 00:00–03:00 arasında "bugün" ve "bu ay" bir gün/ay geriye
kayıyordu.
Formlar useSubmitGuard ile korunur. useTransition'ın pending bayrağı tek
başına yetmiyor: react-hook-form doğrulamayı await ettiği için ikinci tıklama
pending true olmadan geliyor ve tahsilat/gider iki kez yazılabiliyordu.
Paketler sabit fiyatlı ya da kişi başı olabilir (packages.pricing_type).
Rezervasyonda kişi başı seçilirse form, birim fiyat × kişi sayısı hesabını
canlı yapar; ama kaydedilen tek gerçek yine toplam tutardır
(reservation_pricing.gross_amount). Birim fiyat yalnızca dökümü gösterebilmek
ve sonraki düzenlemede yeniden hesaplayabilmek için saklanır — böylece tahsilat
ve kârlılık hesapları tek bir sayıya dayanmaya devam eder.
Her işletmenin subscriptions tablosunda tek satırı var: trial_ends_at
(kayıt + 30 gün, bir daha değişmez) ve access_until (kilidin tek yetkilisi).
Durum kolonu yok, türetiliyor — access_until > trial_ends_at ise abone,
değilse deneme; access_until geçmişse erişim yok.
Tablo businesses'tan ayrı, çünkü oradaki UPDATE politikası kolon ayrımı
yapmıyor: access_until orada olsaydı her owner kendi süresini PostgREST
üzerinden uzatabilirdi. Ayrı tabloda authenticated rolüne yalnızca SELECT
verildi; yazma admin_extend_access() ile ve o da çağıranın
platform_admins listesinde olmasını şart koşuyor.
Süre dolunca requireSession() kullanıcıyı /abonelik'e yönlendiriyor.
Bu bir arayüz kilidi: kullanıcının tokenı RLS tarafında hâlâ geçerli,
doğrudan PostgREST'e istek atan biri yazmaya devam edebilir. Gerçek kilit için
abonelik kontrolünün yazma politikalarına girmesi gerekir.
Ödeme havale ile alınıyor ve elle onaylanıyor: /yonetim ekranından gün
ekleniyor. reference_code havale açıklamasına yazılıyor — gelen ödemeyi
işletmeye bağlayan tek bilgi o.
Tek paket, iki dönem — 6 ay ve 12 ay (src/lib/subscription.ts). Aylık
seçenek yok: ödeme elle onaylandığı için aylıkta bir müşteri için yılda on iki
kez yazışmak gerekirdi. Dönem bedelleri aylık tabandan HESAPLANIYOR, elle
yazılmıyor — böylece "1 ay ücretsiz" ifadesiyle fiyat birbirinden ayrışamıyor.
Kademe de yok:
her yeni özellikte "bu hangi pakette" sorusunu, yükseltme yolunu ve limit
denetimini beraberinde getiriyor; bunun karşılığı ancak çok müşteride çıkar.
Salon sayısına göre de fiyatlanmıyor — ödeme elle onaylandığı için dönem
ortasında salon eklenince tutarın değişmesi her seferinde yazışma demekti.
Bu modül ürüne özel hiçbir şey içermiyor; başka ürünlere taşınabilsin diye
sade tutuldu. Salon sayısı yine /yonetim listesinde görünüyor — büyüyen
müşteriyi (Kurumsal adayını) fark etmek için.
Tüm parasal alanlar numeric(12,2). Arayüzde toplama işlemleri kuruş cinsinden
tamsayı üzerinden yapılır (sumMoney), float birikimi oluşmaz. Gösterim
Intl.NumberFormat("tr-TR") ile: ₺120.000.
src/
app/
(auth)/ giris, kayit + auth server action'ları
(app)/ korumalı panel — her modül kendi actions.ts'i ile
panel/ Dashboard
takvim/ react-big-calendar
rezervasyonlar/[id] detay + tahsilat + gider + kârlılık
musteriler/[id]
gelirler/ giderler/ raporlar/ paketler/ salonlar/ ayarlar/
isletme-kur/ e-posta doğrulaması sonrası işletme kurulumu
components/
ui/ shadcn/ui primitifleri
layout/ Sidebar, PageHeader
shared/ DataTable, FormDialog, Money, DatePicker, VoidDialog…
charts/ recharts tabanlı grafikler
lib/
supabase/ client / server / admin / session
schemas.ts zod şemaları (istemci ve sunucu ortak)
queries.ts sunucu tarafı veri birleştirme
format.ts TR para, tarih, telefon biçimlendirme
supabase/
migrations/ sıralı SQL dosyaları
tests/ PGlite üzerinde şema + RLS testleri
Sunucu eylemleri (actions.ts) her zaman aynı deseni izler:
requireSession() → zod ile doğrula → Supabase → revalidatePath() →
ActionResult döndür. Hatalar toTurkishError() ile kullanıcı diline çevrilir.
getReservationRows sunucu tarafı sayfalamaya geçirilmeli.SUPABASE_SERVICE_ROLE_KEY gerektirir; anahtar tanımlı
değilse arayüz açık bir hata mesajı gösterir.subscriptions kontrolünün yazma politikalarına
eklenmesi gerekir.Sıra önemli: veritabanı önce, uygulama sonra. Aksi halde uygulama var olmayan tablolara sorgu atar.
Supabase → SQL Editor'de supabase/migrations/ altındaki dosyaları sırayla
çalıştırın. Daha önce çalıştırdıklarınızı atlayın; migration'lar idempotent
değildir, ikinci kez çalıştırmak hata verir.
Doğrulama:
select table_name from information_schema.tables
where table_schema = 'public' order by table_name;
leads, quotes, venue_holds, contracts görünüyorsa şema güncel.
Barındırma sağlayıcısında (Vercel → Settings → Environment Variables) ilk dört değişken zorunlu:
| Değişken | Nereden |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | Supabase → Project Settings → API |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Aynı sayfa |
SUPABASE_SERVICE_ROLE_KEY | Aynı sayfa · yalnızca sunucu, asla NEXT_PUBLIC_ yapmayın |
NEXT_PUBLIC_APP_URL | Canlı adres, ör. https://davetpro.com |
BILLING_IBAN | Abonelik sayfasında gösterilecek IBAN · opsiyonel |
BILLING_ACCOUNT_NAME | Havale alıcısının adı · opsiyonel |
BILLING_BANK | Banka adı · opsiyonel |
BILLING_* tanımsızsa abonelik sayfası boş bir kutu göstermek yerine yalnızca
WhatsApp yönlendirmesi çıkarır. NEXT_PUBLIC_ değildir: yalnızca sunucuda
okunur.
NEXT_PUBLIC_APP_URL üretimde zorunludur: personel daveti ve şifre sıfırlama
e-postalarındaki bağlantılar bu adresten üretilir. Tanımsızsa uygulama açık bir
hata verir — sessizce localhost'a düşmez.
Supabase → Authentication → URL Configuration:
https://<alan-adı>/auth/callback ve https://<alan-adı>/girisBu adımı atlarsanız davet ve şifre sıfırlama bağlantıları çalışmaz.
Depoyu GitHub'a gönderip Vercel'de içe aktarmak yeterli; ek yapılandırma
gerekmez. Vercel projeyi Next.js olarak tanır, npm run build çalıştırır.
Dağıtımdan önce yerelde son kontrol:
npm run test && npm run test:audit && npm run build