chore: remove legacy supabase assets
This commit is contained in:
Binary file not shown.
@@ -1,92 +0,0 @@
|
|||||||
---
|
|
||||||
title: Giriş
|
|
||||||
description: Neta'nın ne olduğunu, kimler için tasarlandığını ve hangi problemi çözdüğünü anlatır.
|
|
||||||
order: 1
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Giriş
|
|
||||||
|
|
||||||
> Arşiv: Bu belge Supabase tabanlı Neta v2'yi anlatır. Güncel self-hosted v3 kaynağı repository `README.md` dosyası ve `docs/self-hosted-redesign/phase-8-import-release.md` belgesidir.
|
|
||||||
|
|
||||||
Neta'yı freelancer olarak çalışan birinin işlerini tek yerden yönetebilmesi için tasarladım. Gün içinde müşteri takibi, proje ilerlemesi, görevler, finansal durum, kişisel performans ve bazen de hızlıca soru sorabileceğim bir AI asistan ihtiyacı birbirinden kopuk araçlara dağılıyordu. Neta'nın çıkış noktası bu dağınıklığı azaltmak.
|
|
||||||
|
|
||||||
Bu proje bir ajans yönetim paneli olmaktan çok, bireysel çalışan bir freelancerın kendi operasyonunu düzenli tutması için hazırlanmış bir çalışma alanı. Temel fikir şu: müşterilerimi, projelerimi, görevlerimi, gelir-giderimi, günlük notlarımı ve müşteri portalını aynı sistem içinde görebileyim.
|
|
||||||
|
|
||||||
## Neta Ne İşe Yarar?
|
|
||||||
|
|
||||||
Neta ile şu işleri takip edebilirim:
|
|
||||||
|
|
||||||
- Müşterilerimi ve müşteri pipeline'ımı düzenli tutmak.
|
|
||||||
- Projelerimi durum, bütçe, ilerleme ve teslim tarihiyle yönetmek.
|
|
||||||
- Görevleri proje ve müşteriyle ilişkilendirmek.
|
|
||||||
- Gelir ve giderleri kaydetmek.
|
|
||||||
- Günlük ruh hâli, enerji ve iş memnuniyeti notları tutmak.
|
|
||||||
- Dashboard ve analiz ekranlarıyla genel gidişatı görmek.
|
|
||||||
- AI asistana mevcut verilerim hakkında soru sormak.
|
|
||||||
- Müşterilere sınırlı bir portal açıp proje ilerlemesini göstermek.
|
|
||||||
|
|
||||||
## Bu Repo Ne İçerir?
|
|
||||||
|
|
||||||
Bu repo artık sadece Neta web uygulamasını içerir. Uygulama Next.js ile geliştirilmiştir ve dışarıdan bağlanan bir Supabase projesiyle çalışır.
|
|
||||||
|
|
||||||
Repo içinde şunlar vardır:
|
|
||||||
|
|
||||||
- Next.js uygulama kodu.
|
|
||||||
- Dashboard, portal ve API route'ları.
|
|
||||||
- UI bileşenleri.
|
|
||||||
- Supabase bağlantı katmanı.
|
|
||||||
- Database migration notları ve SQL kayıtları.
|
|
||||||
- Deploy ve geliştirme dokümantasyonu.
|
|
||||||
|
|
||||||
Repo içinde artık şunlar yoktur:
|
|
||||||
|
|
||||||
- Supabase container'ları.
|
|
||||||
- PostgreSQL container'ı.
|
|
||||||
- Docker Compose deployment yapısı.
|
|
||||||
- Otomatik installer scriptleri.
|
|
||||||
- Backup/restore scriptleri.
|
|
||||||
|
|
||||||
Bu kararın sebebi basit: Neta'yı Vercel, Coolify veya Dokploy üzerinde standart bir Next.js uygulaması gibi yayınlamak istiyorum. Veritabanı ve auth tarafını ise Supabase üzerinde ayrıca yönetmek daha temiz ve daha anlaşılır bir kurulum sağlıyor.
|
|
||||||
|
|
||||||
## Kimler İçin Uygun?
|
|
||||||
|
|
||||||
Neta özellikle şu kişiler için uygun:
|
|
||||||
|
|
||||||
- Tek başına çalışan freelancerlar.
|
|
||||||
- Küçük ölçekli müşteri projeleri yöneten geliştiriciler veya tasarımcılar.
|
|
||||||
- Projelerini ve finansal durumunu aynı panelde görmek isteyen bağımsız çalışanlar.
|
|
||||||
- Müşterilerine basit bir proje takip portalı sunmak isteyenler.
|
|
||||||
- Kendi Supabase projesini yönetmekten çekinmeyen kullanıcılar.
|
|
||||||
|
|
||||||
Eğer çok kullanıcılı, ekip rolleri gelişmiş, kurumsal seviyede izin sistemi olan bir SaaS arıyorsanız Neta'nın mevcut hâli bunun için tasarlanmadı. Buradaki odak, tek ana kullanıcı ve onun müşterileri.
|
|
||||||
|
|
||||||
## Genel Akış
|
|
||||||
|
|
||||||
Kurulumdan sonra akış şu şekilde ilerler:
|
|
||||||
|
|
||||||
1. Supabase tarafında gerekli tablo, policy, function ve storage bucket'ları hazırlanır.
|
|
||||||
2. Uygulama repo olarak Vercel, Coolify veya Dokploy'a bağlanır.
|
|
||||||
3. Ortam değişkenleri girilir.
|
|
||||||
4. İlk admin hesabı `/register` üzerinden oluşturulur.
|
|
||||||
5. Dashboard üzerinden müşteri, proje, görev ve finans kayıtları eklenmeye başlanır.
|
|
||||||
6. İstenirse müşterilere portal hesabı oluşturulur.
|
|
||||||
|
|
||||||
Bu dokümantasyonun amacı sadece nasıl kurulur demek değil. Aynı zamanda projeyi neden böyle kurguladığımı, hangi parçanın ne işe yaradığını ve özelleştirmek isteyen birinin nereden başlaması gerektiğini netleştirmek.
|
|
||||||
|
|
||||||
## Canlı Demo
|
|
||||||
|
|
||||||
Neta'yı kurmadan önce incelemek isterseniz demo ortamını açabilirsiniz:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
https://demo.takeneta.com
|
|
||||||
```
|
|
||||||
|
|
||||||
Demo hesabı:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
E-posta: test@takeneta.com
|
|
||||||
Şifre: 123456
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu demo ortamı projeyi hızlıca tanımak için var. Dashboard'u, müşteri ve proje ekranlarını, görevleri, finans alanını, AI asistan ekranını ve client portal akışını kurulum yapmadan gezebilirsiniz.
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
---
|
|
||||||
title: Özellikler
|
|
||||||
description: Neta'nın freelancerlar için sunduğu temel özellikler.
|
|
||||||
order: 2
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Özellikler
|
|
||||||
|
|
||||||
> Arşiv: Bu belge Supabase tabanlı Neta v2'yi anlatır. Güncel self-hosted v3 kaynağı repository `README.md` dosyası ve `docs/self-hosted-redesign/phase-8-import-release.md` belgesidir.
|
|
||||||
|
|
||||||
Neta'yı parça parça özellik eklenmiş bir panel gibi değil, günlük iş akışımı toparlayan bir işletim alanı gibi düşünerek geliştirdim. Her modül tek başına işe yarıyor, ama asıl değer bu modüllerin birbirine bağlanmasında ortaya çıkıyor.
|
|
||||||
|
|
||||||
## Dashboard
|
|
||||||
|
|
||||||
Dashboard, Neta'yı açtığımda genel durumu hızlıca görebilmem için var. Burada gelir-gider özeti, aktif projeler, tamamlanan görevler, mood ve enerji trendleri gibi bilgiler yer alır.
|
|
||||||
|
|
||||||
Dashboard'un amacı detaylı işlem yapmak değil, bana şu soruların cevabını hızlıca vermek:
|
|
||||||
|
|
||||||
- Bu ay işlerin durumu nasıl?
|
|
||||||
- Aktif proje sayım ne?
|
|
||||||
- Son dönemde ne kadar gelir veya gider oluştu?
|
|
||||||
- Görev tamamlama ritmim nasıl?
|
|
||||||
- Kişisel performansım düşüyor mu, yükseliyor mu?
|
|
||||||
|
|
||||||
## Müşteri Yönetimi
|
|
||||||
|
|
||||||
Müşteri modülü, CRM'in sade bir versiyonu gibi çalışır. Burada müşteri adı, firma bilgisi, iletişim bilgileri, web sitesi, notlar ve pipeline aşaması tutulur.
|
|
||||||
|
|
||||||
Müşteriler şu aşamalarda takip edilebilir:
|
|
||||||
|
|
||||||
- Lead
|
|
||||||
- Contacted
|
|
||||||
- Proposal sent
|
|
||||||
- Won
|
|
||||||
- Lost
|
|
||||||
|
|
||||||
Böylece sadece aktif müşterileri değil, görüşme hâlindeki potansiyel işleri de takip etmek mümkün olur.
|
|
||||||
|
|
||||||
## Proje Yönetimi
|
|
||||||
|
|
||||||
Projeler Neta'nın ana omurgalarından biri. Her proje bir müşteriye bağlı olabilir ya da side project olarak bağımsız tutulabilir.
|
|
||||||
|
|
||||||
Bir projede şu bilgiler izlenir:
|
|
||||||
|
|
||||||
- Proje adı.
|
|
||||||
- Açıklama.
|
|
||||||
- Müşteri bağlantısı.
|
|
||||||
- Durum.
|
|
||||||
- Başlangıç ve teslim tarihi.
|
|
||||||
- Bütçe.
|
|
||||||
- Para birimi.
|
|
||||||
- İlerleme yüzdesi.
|
|
||||||
- Kapak görseli.
|
|
||||||
- Planlama bölümleri.
|
|
||||||
- Revizyon kotası.
|
|
||||||
|
|
||||||
Proje ilerlemesi manuel tutulabilir ya da görev durumlarına göre otomatik hesaplanabilir.
|
|
||||||
|
|
||||||
## Görev Yönetimi
|
|
||||||
|
|
||||||
Görevler proje ve müşteriyle ilişkilendirilebilir. Liste ve kanban görünümü vardır. Her görev için öncelik, durum, son tarih, tahmini süre ve gerçekleşen süre kaydedilebilir.
|
|
||||||
|
|
||||||
Görev durumları:
|
|
||||||
|
|
||||||
- `todo`
|
|
||||||
- `in_progress`
|
|
||||||
- `done`
|
|
||||||
|
|
||||||
Öncelikler:
|
|
||||||
|
|
||||||
- `low`
|
|
||||||
- `medium`
|
|
||||||
- `high`
|
|
||||||
- `urgent`
|
|
||||||
|
|
||||||
Müşteriye görünmesi istenen görevler ayrıca işaretlenebilir. Bu sayede client portal içinde sadece seçtiğim görevler gösterilir.
|
|
||||||
|
|
||||||
## Finans Takibi
|
|
||||||
|
|
||||||
Finans modülü gelir ve gider kayıtlarını tutar. Her işlem müşteri veya projeyle ilişkilendirilebilir.
|
|
||||||
|
|
||||||
Kaydedilebilen bilgiler:
|
|
||||||
|
|
||||||
- Gelir veya gider tipi.
|
|
||||||
- Tutar.
|
|
||||||
- Para birimi.
|
|
||||||
- İşlem tarihi.
|
|
||||||
- Kategori.
|
|
||||||
- Ödeme durumu.
|
|
||||||
- Açıklama.
|
|
||||||
|
|
||||||
Bu modül muhasebe programı yerine geçmek için değil, freelancer olarak işin finansal nabzını günlük seviyede görmek için tasarlandı.
|
|
||||||
|
|
||||||
## Günlük ve Kişisel Performans
|
|
||||||
|
|
||||||
Neta'da sadece operasyonel işler yok. Günlük modülüyle mood, enerji ve iş memnuniyeti skorları da tutulur.
|
|
||||||
|
|
||||||
Bu bölümü eklememin sebebi şu: Freelancer çalışırken iş yükü, motivasyon ve enerji birbirinden bağımsız değil. Proje yoğunluğu artarken enerji düşüyorsa bunu görmek değerli.
|
|
||||||
|
|
||||||
Günlük kaydı şunları içerir:
|
|
||||||
|
|
||||||
- Tarih.
|
|
||||||
- Mood skoru.
|
|
||||||
- Enerji skoru.
|
|
||||||
- İş memnuniyeti skoru.
|
|
||||||
- Not.
|
|
||||||
|
|
||||||
## Analizler
|
|
||||||
|
|
||||||
Analizler ekranı, finans ve görev verilerini görselleştirir. Burada proje bazlı gelir dağılımı ve görev durumları gibi özetler görülebilir.
|
|
||||||
|
|
||||||
Bu ekranı, detaylı BI aracı gibi değil, günlük kararları kolaylaştıran sade bir performans ekranı olarak düşündüm.
|
|
||||||
|
|
||||||
## AI Asistan
|
|
||||||
|
|
||||||
AI asistan, Neta içindeki veriler hakkında soru sormak için kullanılır. Örneğin:
|
|
||||||
|
|
||||||
- Bu ay finansal durumum nasıl?
|
|
||||||
- Hangi projeler riskli görünüyor?
|
|
||||||
- Yaklaşan görevlerim neler?
|
|
||||||
- Son günlük kayıtlarıma göre enerjim nasıl gidiyor?
|
|
||||||
|
|
||||||
AI asistan `app_settings` içindeki provider ve API key ayarlarını kullanır. OpenAI, Gemini ve Groq desteği vardır. Ayrıca projede yerel Ollama tabanlı Python yardımcı dosyası da ayrı bir kullanım senaryosu için korunur.
|
|
||||||
|
|
||||||
## Client Portal
|
|
||||||
|
|
||||||
Client portal, müşterilere sınırlı erişim vermek için var. Müşteri kendi hesabıyla giriş yapar ve sadece kendisine bağlı projeleri, public görevleri ve revizyon taleplerini görür.
|
|
||||||
|
|
||||||
Bu portalın amacı müşteriye tam dashboard açmak değil. Sadece şeffaflık sağlamak:
|
|
||||||
|
|
||||||
- Proje ilerlemesi.
|
|
||||||
- Teslim tarihi.
|
|
||||||
- Müşteriye açık görevler.
|
|
||||||
- Revizyon talepleri.
|
|
||||||
|
|
||||||
## Storage ve Dosya Kullanımı
|
|
||||||
|
|
||||||
Neta Supabase Storage kullanır.
|
|
||||||
|
|
||||||
Gerekli bucket'lar:
|
|
||||||
|
|
||||||
- `avatars`: Profil fotoğrafları için.
|
|
||||||
- `project-assets`: Proje kapak görselleri için.
|
|
||||||
|
|
||||||
Avatar bucket'ı public olabilir. Proje görselleri ise private tutulur ve uygulama gerektiğinde signed URL üretir.
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
---
|
|
||||||
title: Mimari
|
|
||||||
description: Neta'nın teknik mimarisi, frontend/backend ayrımı ve Supabase bağlantısı.
|
|
||||||
order: 3
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Mimari
|
|
||||||
|
|
||||||
> Arşiv: Bu mimari Supabase tabanlı Neta v2'ye aittir. Güncel SQLite/Better Auth mimarisi repository `README.md` dosyasında ve ana dönüşüm planında tanımlıdır.
|
|
||||||
|
|
||||||
Neta'yı klasik bir frontend-backend ayrımından çok, Next.js App Router etrafında şekillenen bir uygulama olarak kurguladım. Uygulamanın backend işlerinin büyük bölümü Supabase üzerinde duruyor. Next.js tarafı ise hem arayüzü hem de server action ve API route gibi uygulama mantığını taşıyor.
|
|
||||||
|
|
||||||
## Ana Teknolojiler
|
|
||||||
|
|
||||||
Projede temel olarak şunlar kullanılır:
|
|
||||||
|
|
||||||
- Next.js App Router.
|
|
||||||
- React Server Components.
|
|
||||||
- Client Components.
|
|
||||||
- Server Actions.
|
|
||||||
- Supabase Auth.
|
|
||||||
- Supabase Postgres.
|
|
||||||
- Supabase Storage.
|
|
||||||
- Supabase RLS.
|
|
||||||
- Vercel AI SDK.
|
|
||||||
- Tailwind CSS.
|
|
||||||
- Poyraz UI.
|
|
||||||
|
|
||||||
Bu yapı sayesinde ayrı bir Express/Nest backend yazmadan, server tarafı işler Next.js içinde yönetilebilir.
|
|
||||||
|
|
||||||
## Route Yapısı
|
|
||||||
|
|
||||||
Uygulama `app/` klasörü altında organize edilir.
|
|
||||||
|
|
||||||
Ana alanlar:
|
|
||||||
|
|
||||||
- `app/(dashboard)`: Freelancer dashboard ekranları.
|
|
||||||
- `app/portal`: Müşteri portalı.
|
|
||||||
- `app/login`: Giriş ekranı.
|
|
||||||
- `app/register`: İlk admin kayıt ekranı.
|
|
||||||
- `app/api`: AI ve yardımcı API route'ları.
|
|
||||||
|
|
||||||
Dashboard route'ları freelancer kullanıcısı için tasarlanmıştır. Portal route'ları ise `client` rolündeki kullanıcıları hedefler.
|
|
||||||
|
|
||||||
## Layout ve Rol Kontrolü
|
|
||||||
|
|
||||||
Dashboard layout içinde kullanıcı profili alınır. Eğer kullanıcının rolü `client` ise dashboard yerine `/portal` tarafına yönlendirilir.
|
|
||||||
|
|
||||||
Portal layout ise bunun tersini yapar. Kullanıcının rolü `client` değilse portal'a erişmesine izin verilmez.
|
|
||||||
|
|
||||||
Bu ayrım uygulama seviyesinde yapılır, ama asıl güvenlik Supabase RLS politikalarıyla sağlanır. Yani UI yanlışlıkla veri göstermeye çalışsa bile Supabase policy'leri yetkisiz veriyi döndürmemelidir.
|
|
||||||
|
|
||||||
## Supabase Client Katmanları
|
|
||||||
|
|
||||||
Projede üç farklı Supabase bağlantısı vardır:
|
|
||||||
|
|
||||||
- Browser client: Client component içinde kullanılır.
|
|
||||||
- Server client: Server component, server action ve route handler içinde kullanılır.
|
|
||||||
- Service role client: Sadece server tarafında, özel yetki gereken işlemler için kullanılır.
|
|
||||||
|
|
||||||
Service role client özellikle şu işler için gereklidir:
|
|
||||||
|
|
||||||
- İlk admin kullanıcısını oluşturmak.
|
|
||||||
- Client portal hesabı oluşturmak.
|
|
||||||
- Storage'a server-side upload yapmak.
|
|
||||||
|
|
||||||
Bu yüzden `SUPABASE_SERVICE_ROLE_KEY` kesinlikle client tarafına çıkarılmamalıdır.
|
|
||||||
|
|
||||||
## Middleware
|
|
||||||
|
|
||||||
`proxy.ts` içinde Supabase session güncellemesi yapılır. Giriş gerektiren route'larda kullanıcı yoksa `/login` sayfasına yönlendirilir.
|
|
||||||
|
|
||||||
Public auth route'ları bu kontrolden ayrı tutulur:
|
|
||||||
|
|
||||||
- `/login`
|
|
||||||
- `/register`
|
|
||||||
- `/auth`
|
|
||||||
- `/forgot-password`
|
|
||||||
|
|
||||||
Bu ayrım özellikle önemlidir. Login ekranının açılması için Supabase session doğrulamasını beklemek gereksiz bir blok yaratabilir. Bu yüzden public route'larda middleware dış servisi beklemeden geçer.
|
|
||||||
|
|
||||||
## Server Components ve Client Components
|
|
||||||
|
|
||||||
Neta'da genel desen şudur:
|
|
||||||
|
|
||||||
- Sayfa dosyası server component olarak veri çeker.
|
|
||||||
- UI ve etkileşim yoğun kısım client component'e aktarılır.
|
|
||||||
- CRUD işlemleri `actions.ts` dosyalarında server action olarak tutulur.
|
|
||||||
|
|
||||||
Örnek:
|
|
||||||
|
|
||||||
- `app/(dashboard)/tasks/page.tsx`: Veriyi çeker.
|
|
||||||
- `app/(dashboard)/tasks/tasks-client.tsx`: Liste, filtre, dialog ve kanban UI'ını yönetir.
|
|
||||||
- `app/(dashboard)/tasks/actions.ts`: Görev oluşturma, güncelleme ve silme işlemlerini yapar.
|
|
||||||
|
|
||||||
Bu desen projeyi okunabilir tutuyor.
|
|
||||||
|
|
||||||
## Veritabanı Güvenliği
|
|
||||||
|
|
||||||
Neta'da veri izolasyonunun ana mantığı `user_id` alanıdır. Freelancer verilerinde genellikle şu policy mantığı kullanılır:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
auth.uid() = user_id
|
|
||||||
```
|
|
||||||
|
|
||||||
Müşteri portalı için ayrı bir ilişki vardır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
profiles.role = client
|
|
||||||
clients.client_auth_id = auth.users.id
|
|
||||||
```
|
|
||||||
|
|
||||||
Müşteri sadece kendi client kaydına bağlı projeleri ve public işaretlenmiş görevleri görebilir.
|
|
||||||
|
|
||||||
## AI Mimarisi
|
|
||||||
|
|
||||||
AI tarafında iki ana akış var:
|
|
||||||
|
|
||||||
- Chat ekranı: `/api/chat`
|
|
||||||
- Analiz endpoint'leri: `/api/finance-analysis`, `/api/project-risk`
|
|
||||||
|
|
||||||
Chat endpoint'i kullanıcının son görev, proje, finans ve günlük kayıtlarından bir bağlam oluşturur. Bu bağlam AI modele sistem mesajı olarak verilir.
|
|
||||||
|
|
||||||
Provider ayarları `app_settings` tablosundan okunur. Böylece kullanıcı OpenAI, Gemini veya Groq arasında seçim yapabilir.
|
|
||||||
|
|
||||||
## Docker Neden Yok?
|
|
||||||
|
|
||||||
Önceki denemelerde full-stack self-host yaklaşımı vardı. Bu yaklaşım Supabase, Postgres, Auth, Storage ve uygulamayı aynı deployment içine almaya çalışıyordu. Çalışır bir fikir olsa da kurulum ve bakım maliyeti fazlaydı.
|
|
||||||
|
|
||||||
Şimdiki hedef daha sade:
|
|
||||||
|
|
||||||
- Supabase dışarıda yönetilir.
|
|
||||||
- Neta sadece Next.js uygulaması olarak deploy edilir.
|
|
||||||
- Vercel, Coolify veya Dokploy standart akışları kullanılır.
|
|
||||||
|
|
||||||
Bu sayede proje daha az özel kurulum bilgisine ihtiyaç duyar ve GitHub reposundan deploy etmek kolaylaşır.
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
---
|
|
||||||
title: Supabase Kurulumu
|
|
||||||
description: Neta için gerekli Supabase tablo, policy, function ve storage gereksinimleri.
|
|
||||||
order: 4
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Supabase Kurulumu
|
|
||||||
|
|
||||||
> Arşiv: Self-hosted v3 çalışmak için Supabase kurulumu istemez. Bu içerik yalnızca eski bir v2 instance'ını anlamak içindir; güncel aktarım rehberi `docs/self-hosted-redesign/phase-8-import-release.md` dosyasındadır.
|
|
||||||
|
|
||||||
Neta'nın çalışması için hazır bir Supabase projesine ihtiyaç var. Bu repo artık Supabase'i beraberinde getirmiyor. Yani uygulama deploy edilirken veritabanı, auth veya storage otomatik kurulmaz. Ben burada web uygulamasını sade tutmayı tercih ettim. Supabase tarafı ayrı bir altyapı olarak yönetilir.
|
|
||||||
|
|
||||||
> Şuan nestjs ile backend geliştiriyorum, çok yakında tam self host olacak :)
|
|
||||||
|
|
||||||
## Beklenen Kurulum
|
|
||||||
|
|
||||||
Kurulumda genel yaklaşım şu:
|
|
||||||
|
|
||||||
1. Supabase üzerinde bir proje oluşturulur.
|
|
||||||
2. Neta'nın ihtiyaç duyduğu SQL schema, policy, trigger ve function'lar Supabase tarafına uygulanır.
|
|
||||||
3. Gerekli storage bucket'ları oluşturulur.
|
|
||||||
4. Supabase URL ve key değerleri uygulama ortam değişkenlerine eklenir.
|
|
||||||
5. Uygulama deploy edilir.
|
|
||||||
|
|
||||||
Repo içinde `supabase/` ve `docs/database/` klasörleri tutulmaya devam eder. Bunları deployment sırasında otomatik çalıştıran bir script yoktur. Bu dosyalar benim SQL kayıtlarım, migration notlarım ve schema geçmişim olarak durur.
|
|
||||||
|
|
||||||
## Tek Seferlik Kurulum SQL'i
|
|
||||||
|
|
||||||
Yeni bir Supabase projesini Neta için hazırlamanın en kolay yolu `supabase/setup.sql` dosyasını çalıştırmak. Bu dosya `supabase/schema.sql` ve `supabase/migrations/0002..0012` arasındaki migration dosyalarının tek dosyada birleştirilmiş hâlidir.
|
|
||||||
|
|
||||||
Demo seed verisi bu dosyaya dahil değildir. Production kurulumda örnek veri istemediğim için seed ayrı tutulur.
|
|
||||||
|
|
||||||
### Supabase SQL Editor ile
|
|
||||||
|
|
||||||
Supabase panelinde şu bölümü açın:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
SQL Editor -> New query
|
|
||||||
```
|
|
||||||
|
|
||||||
Sonra `supabase/setup.sql` dosyasının tamamını kopyalayıp tek seferde çalıştırın.
|
|
||||||
|
|
||||||
Bu işlem şunları kurar:
|
|
||||||
|
|
||||||
- Tablolar.
|
|
||||||
- Foreign key ve check constraint'leri.
|
|
||||||
- Index'ler.
|
|
||||||
- Trigger ve function'lar.
|
|
||||||
- RLS policy'leri.
|
|
||||||
- Storage bucket kayıtları ve storage policy'leri.
|
|
||||||
|
|
||||||
### Terminalden Tek Komut ile
|
|
||||||
|
|
||||||
Supabase connection string'iniz varsa aynı kurulumu tek `psql` komutuyla da çalıştırabilirsiniz:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
psql "postgresql://postgres:[PASSWORD]@[HOST]:5432/postgres" -v ON_ERROR_STOP=1 -f supabase/setup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
Supabase hosted projelerde connection string'i şuradan alabilirsiniz:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Project Settings -> Database -> Connection string
|
|
||||||
```
|
|
||||||
|
|
||||||
Şifre, host ve port bilgilerini kendi projenize göre değiştirmelisiniz.
|
|
||||||
|
|
||||||
Bu komut başarılı bittikten sonra Supabase tarafı Neta uygulamasına hazır hâle gelir.
|
|
||||||
|
|
||||||
## Gerekli Tablolar
|
|
||||||
|
|
||||||
Neta şu public tabloları bekler:
|
|
||||||
|
|
||||||
- `profiles`
|
|
||||||
- `clients`
|
|
||||||
- `projects`
|
|
||||||
- `tasks`
|
|
||||||
- `calendar_events`
|
|
||||||
- `finance_transactions`
|
|
||||||
- `daily_logs`
|
|
||||||
- `app_settings`
|
|
||||||
- `chat_sessions`
|
|
||||||
- `chat_messages`
|
|
||||||
- `project_planning_sections`
|
|
||||||
- `project_revisions`
|
|
||||||
- `client_activities`
|
|
||||||
- `proposals`
|
|
||||||
- `contracts`
|
|
||||||
- `invoices`
|
|
||||||
- `subscriptions`
|
|
||||||
- `document_embeddings`
|
|
||||||
- `journals`
|
|
||||||
|
|
||||||
`journals` tablosu eski yapıdan gelen legacy bir tablodur. Güncel günlük ekranı ağırlıklı olarak `daily_logs` kullanır, ama schema geçmişinde `journals` hâlâ durur.
|
|
||||||
|
|
||||||
## Gerekli Function ve RPC'ler
|
|
||||||
|
|
||||||
Uygulama şu function/RPC yapılarına ihtiyaç duyar:
|
|
||||||
|
|
||||||
- `is_first_admin_setup_available`
|
|
||||||
- `request_internal_auth_creation`
|
|
||||||
- `match_documents`
|
|
||||||
- `get_dashboard_metrics` — Dashboard KPI ve grafik verilerini sunucu tarafında hesaplar
|
|
||||||
- `get_analytics_metrics` — Analiz sayfası proje gelir dağılımı ve görev istatistiklerini hesaplar
|
|
||||||
- Yeni auth user için profile oluşturan trigger/function
|
|
||||||
- Proje ilerlemesini görev durumuna göre güncelleyen trigger/function
|
|
||||||
|
|
||||||
Özellikle `request_internal_auth_creation`, service role ile güvenli kullanıcı oluşturma akışında kullanılır. İlk admin ve müşteri portal kullanıcıları oluşturulurken bu yapı önemlidir.
|
|
||||||
|
|
||||||
## Row Level Security
|
|
||||||
|
|
||||||
Neta'da RLS açık olmalıdır. Freelancer verilerinin çoğu şu mantıkla ayrılır:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
auth.uid() = user_id
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu sayede her kullanıcı sadece kendi verisini görür.
|
|
||||||
|
|
||||||
Müşteri portalı ise farklı çalışır. Müşterinin auth kullanıcısı `clients.client_auth_id` alanına bağlanır. Portal erişimi bu ilişki üzerinden verilir.
|
|
||||||
|
|
||||||
Örnek mantık:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
client auth user -> clients.client_auth_id -> projects.client_id
|
|
||||||
```
|
|
||||||
|
|
||||||
Müşteri sadece kendisine bağlı projeleri, public işaretli görevleri ve kendi revizyon taleplerini görmelidir.
|
|
||||||
|
|
||||||
## Storage Bucket'ları
|
|
||||||
|
|
||||||
Supabase Storage tarafında iki bucket beklenir.
|
|
||||||
|
|
||||||
| Bucket | Public | Dosya boyutu | MIME türleri |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `avatars` | Evet | Supabase varsayılanı | Herhangi |
|
|
||||||
| `project-assets` | Hayır | 5 MB | `image/jpeg`, `image/png`, `image/webp`, `image/gif` |
|
|
||||||
|
|
||||||
`avatars` public olabilir çünkü profil fotoğrafları direkt gösterilir. `project-assets` private kalmalıdır. Proje görselleri için uygulama signed URL üretir.
|
|
||||||
|
|
||||||
## Service Role Key Neden Gerekli?
|
|
||||||
|
|
||||||
Uygulama normal kullanıcı işlemlerinde anon key ve session kullanır. Ama bazı işler server tarafında daha yüksek yetki gerektirir:
|
|
||||||
|
|
||||||
- İlk admin auth kullanıcısını oluşturmak.
|
|
||||||
- Müşteri portal kullanıcısı oluşturmak.
|
|
||||||
- Avatar veya proje görseli upload etmek.
|
|
||||||
- Bazı fallback setup kontrollerini yapmak.
|
|
||||||
|
|
||||||
Bu yüzden `SUPABASE_SERVICE_ROLE_KEY` zorunludur. Bu key asla browser'a gönderilmemelidir.
|
|
||||||
|
|
||||||
## SQL Dosyaları Nasıl Kullanılmalı?
|
|
||||||
|
|
||||||
Yeni kurulum için en pratik dosya:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
supabase/setup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu dosya tek seferde çalıştırılacak kurulum dosyasıdır.
|
|
||||||
|
|
||||||
Parçalı dosyalar ise migration geçmişini ve geliştirme sürecini belgelemek için korunur:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
supabase/schema.sql
|
|
||||||
supabase/migrations/
|
|
||||||
docs/database/
|
|
||||||
```
|
|
||||||
|
|
||||||
Yeni bir kurulum yaparken en güvenilir kaynak, çalışan Supabase projesinden aldığınız güncel SQL çıktısıdır. Supabase panelindeki "copy as SQL" çıktısı bu yüzden önemlidir.
|
|
||||||
|
|
||||||
Eğer schema'yı elle hazırlıyorsanız:
|
|
||||||
|
|
||||||
1. Önce extension'ları açın.
|
|
||||||
2. Tabloları oluşturun.
|
|
||||||
3. Foreign key ve check constraint'leri ekleyin.
|
|
||||||
4. Function ve trigger'ları ekleyin.
|
|
||||||
5. RLS'i aktif edin.
|
|
||||||
6. Policy'leri ekleyin.
|
|
||||||
7. Storage bucket ve policy'leri ayarlayın.
|
|
||||||
|
|
||||||
Bu sıra bozulursa Supabase SQL editor bazı foreign key veya policy adımlarında hata verebilir.
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
---
|
|
||||||
title: Environment Ayarları
|
|
||||||
description: Neta'yı çalıştırmak için gereken ortam değişkenleri.
|
|
||||||
order: 5
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Environment Ayarları
|
|
||||||
|
|
||||||
> Arşiv: Aşağıdaki Supabase env sözleşmesi v2'ye aittir. Güncel environment sözleşmesi `.env.example`, repository `README.md` ve Faz 8 runbook'unda tanımlıdır.
|
|
||||||
|
|
||||||
Neta'nın çalışması için az sayıda ama kritik ortam değişkeni gerekiyor. Uygulama artık Supabase'i kendi içinde ayağa kaldırmadığı için bütün Supabase bağlantı bilgileri dışarıdan verilir.
|
|
||||||
|
|
||||||
Local geliştirme için `.env.example` dosyasını `.env.local` olarak kopyalayabilirsiniz. Production tarafında ise aynı değerleri Vercel, Coolify veya Dokploy panelinden environment variable olarak eklemek gerekir.
|
|
||||||
|
|
||||||
## Gerekli Değişkenler
|
|
||||||
|
|
||||||
```env
|
|
||||||
NEXT_PUBLIC_SITE_URL=https://your-domain.com
|
|
||||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
|
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
||||||
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
|
||||||
```
|
|
||||||
|
|
||||||
## `NEXT_PUBLIC_SITE_URL`
|
|
||||||
|
|
||||||
Uygulamanın dışarıdan açıldığı URL'dir.
|
|
||||||
|
|
||||||
Local geliştirmede:
|
|
||||||
|
|
||||||
```env
|
|
||||||
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
Production'da:
|
|
||||||
|
|
||||||
```env
|
|
||||||
NEXT_PUBLIC_SITE_URL=https://neta.example.com
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu değer özellikle link üretme, callback ve ileride e-posta akışları gibi noktalarda işe yarar.
|
|
||||||
|
|
||||||
## `NEXT_PUBLIC_SUPABASE_URL`
|
|
||||||
|
|
||||||
Supabase project API URL değeridir.
|
|
||||||
|
|
||||||
Supabase panelinde şu bölümden alınır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Project Settings -> API -> Project URL
|
|
||||||
```
|
|
||||||
|
|
||||||
Format genellikle şöyledir:
|
|
||||||
|
|
||||||
```env
|
|
||||||
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu değer public olabilir. Browser tarafındaki Supabase client da bu URL'yi kullanır.
|
|
||||||
|
|
||||||
## `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
|
||||||
|
|
||||||
Supabase anon/public key değeridir.
|
|
||||||
|
|
||||||
Supabase panelinde şu bölümden alınır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Project Settings -> API -> Project API keys -> anon public
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu değer browser tarafında kullanılabilir. Zaten `NEXT_PUBLIC_` prefix'i bu yüzden vardır. Güvenlik bu key'in gizli olmasından değil, Supabase RLS policy'lerinin doğru yazılmış olmasından gelir.
|
|
||||||
|
|
||||||
## `SUPABASE_SERVICE_ROLE_KEY`
|
|
||||||
|
|
||||||
Bu en hassas değişkendir. Supabase service role key, RLS'i bypass edebilir. Bu yüzden asla `NEXT_PUBLIC_` prefix'iyle tanımlanmamalıdır.
|
|
||||||
|
|
||||||
Supabase panelinde şu bölümden alınır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Project Settings -> API -> Project API keys -> service_role
|
|
||||||
```
|
|
||||||
|
|
||||||
Neta bu key'i sadece server tarafında kullanır.
|
|
||||||
|
|
||||||
Kullanıldığı yerler:
|
|
||||||
|
|
||||||
- İlk admin kullanıcısı oluşturma.
|
|
||||||
- Müşteri portal kullanıcısı oluşturma.
|
|
||||||
- Storage upload işlemleri.
|
|
||||||
- Bazı setup fallback kontrolleri.
|
|
||||||
|
|
||||||
## Local `.env.local` Örneği
|
|
||||||
|
|
||||||
```env
|
|
||||||
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
|
||||||
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
|
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=ey...
|
|
||||||
SUPABASE_SERVICE_ROLE_KEY=ey...
|
|
||||||
```
|
|
||||||
|
|
||||||
`.env.local` dosyası repoya commit edilmemelidir.
|
|
||||||
|
|
||||||
## Production Env Kontrol Listesi
|
|
||||||
|
|
||||||
Deploy etmeden önce şunları kontrol ederim:
|
|
||||||
|
|
||||||
- `NEXT_PUBLIC_SUPABASE_URL` boş değil.
|
|
||||||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY` doğru projeye ait.
|
|
||||||
- `SUPABASE_SERVICE_ROLE_KEY` doğru projeye ait.
|
|
||||||
- Production domain `NEXT_PUBLIC_SITE_URL` içine yazılmış.
|
|
||||||
- Supabase Auth tarafında site URL ve redirect URL ayarları production domain ile uyumlu.
|
|
||||||
|
|
||||||
## Yaygın Hatalar
|
|
||||||
|
|
||||||
### Login Açılıyor Ama Kayıt Oluşturulamıyor
|
|
||||||
|
|
||||||
Genellikle `SUPABASE_SERVICE_ROLE_KEY` eksiktir veya yanlış projeye aittir.
|
|
||||||
|
|
||||||
### Veriler Görünmüyor
|
|
||||||
|
|
||||||
Bu çoğu zaman env hatası değil, RLS policy hatasıdır. Kullanıcı session'ı doğru olsa bile policy izin vermiyorsa Supabase boş sonuç döndürür.
|
|
||||||
|
|
||||||
### Storage Upload Çalışmıyor
|
|
||||||
|
|
||||||
Bucket adı, bucket policy veya service role key kontrol edilmelidir.
|
|
||||||
|
|
||||||
### AI Asistan Çalışmıyor
|
|
||||||
|
|
||||||
AI provider ve API key ayarları uygulama içindeki Ayarlar ekranından kaydedilir. Ortam değişkeni olarak değil, `app_settings` tablosu üzerinden yönetilir.
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
---
|
|
||||||
title: Vercel ile Deploy
|
|
||||||
description: Neta'yı Vercel üzerinde yayınlama adımları.
|
|
||||||
order: 6
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Vercel ile Deploy
|
|
||||||
|
|
||||||
> Arşiv: SQLite ve persistent local volume kullanan self-hosted v3 için Vercel hedef runtime değildir. Docker tabanlı güncel kurulum repository `README.md` dosyasındadır.
|
|
||||||
|
|
||||||
Neta'yı Vercel üzerinde yayınlamak en sade yollardan biri. Çünkü proje standart bir Next.js uygulaması olarak çalışır. Dockerfile, Compose veya özel installer gerekmez.
|
|
||||||
|
|
||||||
Benim önerdiğim akış şu: Supabase projesini önce hazırlayın, sonra GitHub reposunu Vercel'e bağlayın.
|
|
||||||
|
|
||||||
## Ön Hazırlık
|
|
||||||
|
|
||||||
Deploy'dan önce hazır olması gerekenler:
|
|
||||||
|
|
||||||
- GitHub üzerinde Neta reposu.
|
|
||||||
- Supabase projesi.
|
|
||||||
- Supabase tabloları, policy'leri, function'ları ve storage bucket'ları.
|
|
||||||
- Environment variable değerleri.
|
|
||||||
|
|
||||||
Supabase tarafını yeni kuruyorsanız önce `supabase/setup.sql` dosyasını Supabase SQL Editor'da tek seferde çalıştırın. Terminal kullanıyorsanız aynı işlem şu komutla yapılabilir:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
psql "postgresql://postgres:[PASSWORD]@[HOST]:5432/postgres" -v ON_ERROR_STOP=1 -f supabase/setup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
Gerekli env değerleri:
|
|
||||||
|
|
||||||
```env
|
|
||||||
NEXT_PUBLIC_SITE_URL=https://your-domain.com
|
|
||||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
|
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
||||||
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
|
||||||
```
|
|
||||||
|
|
||||||
## Vercel'e Repo Bağlama
|
|
||||||
|
|
||||||
1. Vercel dashboard'a girin.
|
|
||||||
2. `Add New Project` seçin.
|
|
||||||
3. GitHub üzerinden Neta reposunu seçin.
|
|
||||||
4. Framework olarak Next.js otomatik algılanır.
|
|
||||||
5. Build ayarlarını varsayılan bırakabilirsiniz.
|
|
||||||
|
|
||||||
Vercel genelde şu komutları kendisi ayarlar:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Start command girmeniz gerekmez. Vercel Next.js runtime'ını kendi yönetir.
|
|
||||||
|
|
||||||
## Environment Variable Ekleme
|
|
||||||
|
|
||||||
Vercel proje ayarlarında şu bölüme girin:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Project Settings -> Environment Variables
|
|
||||||
```
|
|
||||||
|
|
||||||
Değerleri production, preview ve development ortamlarına ihtiyaca göre ekleyin.
|
|
||||||
|
|
||||||
Önemli not:
|
|
||||||
|
|
||||||
`SUPABASE_SERVICE_ROLE_KEY` server-only kalmalıdır. Vercel'de normal environment variable olarak eklemek yeterlidir. `NEXT_PUBLIC_` prefix'i vermeyin.
|
|
||||||
|
|
||||||
## Supabase Auth Ayarları
|
|
||||||
|
|
||||||
Supabase tarafında şu ayarlar production domain ile uyumlu olmalıdır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Authentication -> URL Configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
Önerilen değerler:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Site URL: https://your-domain.com
|
|
||||||
Redirect URLs:
|
|
||||||
https://your-domain.com/**
|
|
||||||
```
|
|
||||||
|
|
||||||
Eğer preview deployment kullanıyorsanız Vercel preview domainlerini de eklemek isteyebilirsiniz.
|
|
||||||
|
|
||||||
## İlk Deploy Sonrası
|
|
||||||
|
|
||||||
Deploy tamamlandıktan sonra şu adımları izlerim:
|
|
||||||
|
|
||||||
1. Uygulamayı production domain üzerinden açarım.
|
|
||||||
2. `/api/health` endpoint'ini kontrol ederim.
|
|
||||||
3. `/register` sayfasından ilk admin hesabını oluştururum.
|
|
||||||
4. Login olurum.
|
|
||||||
5. Dashboard açılıyor mu bakarım.
|
|
||||||
6. Bir müşteri, proje ve görev ekleyerek Supabase bağlantısını test ederim.
|
|
||||||
7. Profil fotoğrafı veya proje görseli upload ederek storage policy'lerini kontrol ederim.
|
|
||||||
|
|
||||||
## İlk Admin
|
|
||||||
|
|
||||||
İlk admin hesabı `/register` üzerinden oluşturulur. İlk profil oluştuğu anda public kayıt akışı kapanır.
|
|
||||||
|
|
||||||
Bu davranış güvenlik için önemli. Neta kişisel self-host bir çalışma alanı olarak tasarlandığı için herkesin kayıt olup sisteme girmesini istemem.
|
|
||||||
|
|
||||||
## Vercel'de Dikkat Edilecekler
|
|
||||||
|
|
||||||
### Build Başarılı Ama Sayfa Veri Göstermiyor
|
|
||||||
|
|
||||||
Supabase env değerlerini kontrol edin. Yanlış Supabase projesine bağlanıyor olabilirsiniz.
|
|
||||||
|
|
||||||
### Register Çalışmıyor
|
|
||||||
|
|
||||||
`SUPABASE_SERVICE_ROLE_KEY` eksik veya yanlış olabilir. Ayrıca `request_internal_auth_creation` RPC'si Supabase tarafında bulunmalıdır.
|
|
||||||
|
|
||||||
### Storage Upload Hata Veriyor
|
|
||||||
|
|
||||||
Bucket adlarını ve storage policy'lerini kontrol edin:
|
|
||||||
|
|
||||||
- `avatars`
|
|
||||||
- `project-assets`
|
|
||||||
|
|
||||||
### AI Asistan Cevap Vermiyor
|
|
||||||
|
|
||||||
Uygulama içindeki Ayarlar ekranından AI provider ve API key kaydedilmelidir.
|
|
||||||
|
|
||||||
## Vercel İçin Kısa Kontrol Listesi
|
|
||||||
|
|
||||||
- GitHub repo bağlandı.
|
|
||||||
- Next.js framework algılandı.
|
|
||||||
- Env variable'lar girildi.
|
|
||||||
- Supabase Auth URL ayarları yapıldı.
|
|
||||||
- Supabase schema ve RLS hazır.
|
|
||||||
- Storage bucket'ları oluşturuldu.
|
|
||||||
- İlk admin oluşturuldu.
|
|
||||||
- Dashboard veri yazıp okuyabiliyor.
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
---
|
|
||||||
title: Coolify ile Deploy
|
|
||||||
description: Neta'yı bir VPS üzerinde Coolify kullanarak yayınlama.
|
|
||||||
order: 7
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Coolify ile Deploy
|
|
||||||
|
|
||||||
> Arşiv: Aşağıdaki harici Supabase/Nixpacks akışı v2'ye aittir. v3 için Dockerfile, tek replica ve `/app/data` persistent volume sözleşmesini `docs/self-hosted-redesign/phase-8-import-release.md` belgesinden uygulayın.
|
|
||||||
|
|
||||||
Coolify, kendi VPS'im üzerinde uygulama yayınlamak istediğimde pratik bir seçenek. Neta artık Docker Compose ile gelen full-stack bir paket olmadığı için Coolify üzerinde standart bir Next.js uygulaması olarak deploy edilebilir.
|
|
||||||
|
|
||||||
Buradaki temel fikir şu: Coolify sadece Neta web uygulamasını çalıştırır. Supabase ayrı bir servis olarak dışarıdadır.
|
|
||||||
|
|
||||||
## Ön Hazırlık
|
|
||||||
|
|
||||||
Coolify deploy için hazır olması gerekenler:
|
|
||||||
|
|
||||||
- Bir VPS.
|
|
||||||
- Kurulu ve çalışan Coolify.
|
|
||||||
- GitHub üzerinde Neta reposu.
|
|
||||||
- Harici Supabase projesi.
|
|
||||||
- Supabase env değerleri.
|
|
||||||
|
|
||||||
Supabase tarafında tablo, policy, function ve storage bucket'ları hazır olmalıdır.
|
|
||||||
|
|
||||||
Yeni bir Supabase projesi hazırlıyorsanız `supabase/setup.sql` dosyasını tek seferde çalıştırabilirsiniz:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
psql "postgresql://postgres:[PASSWORD]@[HOST]:5432/postgres" -v ON_ERROR_STOP=1 -f supabase/setup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
Supabase panelinden ilerlemek isterseniz aynı dosyanın tamamını SQL Editor'a yapıştırıp çalıştırmanız yeterli.
|
|
||||||
|
|
||||||
## Uygulama Oluşturma
|
|
||||||
|
|
||||||
Coolify içinde yeni bir application oluştururken:
|
|
||||||
|
|
||||||
1. Kaynak olarak GitHub reposunu seçin.
|
|
||||||
2. Uygulama tipi olarak Node.js veya Next.js seçin.
|
|
||||||
3. Docker Compose seçmeyin.
|
|
||||||
4. Dockerfile'a ihtiyaç yoktur.
|
|
||||||
|
|
||||||
Kullanılacak komutlar:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
npm run start
|
|
||||||
```
|
|
||||||
|
|
||||||
Eğer Coolify framework'ü otomatik algılıyorsa bu komutları kendisi de önerebilir.
|
|
||||||
|
|
||||||
## Port Ayarı
|
|
||||||
|
|
||||||
Next.js production server varsayılan olarak `3000` portunda çalışır.
|
|
||||||
|
|
||||||
Coolify içinde internal port olarak şunu kullanabilirsiniz:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
3000
|
|
||||||
```
|
|
||||||
|
|
||||||
Domain ve SSL yönlendirmesini Coolify üzerinden yapabilirsiniz.
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
Coolify application ayarlarında şu değerleri ekleyin:
|
|
||||||
|
|
||||||
```env
|
|
||||||
NEXT_PUBLIC_SITE_URL=https://neta.example.com
|
|
||||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
|
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
||||||
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
|
||||||
```
|
|
||||||
|
|
||||||
`SUPABASE_SERVICE_ROLE_KEY` değeri gizli tutulmalıdır. Coolify secret/environment değişkeni olarak tanımlamak yeterlidir.
|
|
||||||
|
|
||||||
## Supabase Auth Ayarları
|
|
||||||
|
|
||||||
Supabase panelinde production domain'i tanımlayın:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Authentication -> URL Configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
Örnek:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Site URL: https://neta.example.com
|
|
||||||
Redirect URLs:
|
|
||||||
https://neta.example.com/**
|
|
||||||
```
|
|
||||||
|
|
||||||
## Health Check
|
|
||||||
|
|
||||||
Neta'da basit bir health endpoint vardır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/api/health
|
|
||||||
```
|
|
||||||
|
|
||||||
Coolify health check ayarında bu endpoint'i kullanabilirsiniz. Beklenen yanıt JSON olarak `status: ok` bilgisidir.
|
|
||||||
|
|
||||||
## İlk Admin Kurulumu
|
|
||||||
|
|
||||||
Deploy tamamlandıktan sonra:
|
|
||||||
|
|
||||||
1. Domain üzerinden uygulamayı açın.
|
|
||||||
2. `/register` sayfasına gidin.
|
|
||||||
3. İlk admin hesabını oluşturun.
|
|
||||||
4. Login olun.
|
|
||||||
|
|
||||||
İlk admin oluştuğunda kayıt akışı kapanır. Sonraki kullanıcılar ancak uygulama içinden müşteri portal hesabı olarak oluşturulabilir.
|
|
||||||
|
|
||||||
## Coolify'da Sık Karşılaşılan Durumlar
|
|
||||||
|
|
||||||
### Build Takılıyor
|
|
||||||
|
|
||||||
Önce Node.js versiyonunu kontrol edin. Node.js 20 veya üzeri kullanmak daha sağlıklı olur.
|
|
||||||
|
|
||||||
### Uygulama Açılıyor Ama Login Sonrası Veri Yok
|
|
||||||
|
|
||||||
Genellikle Supabase env değerleri yanlış projeyi gösteriyordur ya da RLS policy'leri doğru değildir.
|
|
||||||
|
|
||||||
### Register Hata Veriyor
|
|
||||||
|
|
||||||
Service role key eksik olabilir. Ayrıca Supabase tarafında `request_internal_auth_creation` function'ı bulunmalıdır.
|
|
||||||
|
|
||||||
### Görsel Upload Hata Veriyor
|
|
||||||
|
|
||||||
Storage bucket'ları ve policy'leri kontrol edin. `project-assets` private olmalı, `avatars` public olabilir.
|
|
||||||
|
|
||||||
## Coolify İçin Kısa Kontrol Listesi
|
|
||||||
|
|
||||||
- GitHub repo bağlandı.
|
|
||||||
- Docker Compose kullanılmadı.
|
|
||||||
- Install command `npm install`.
|
|
||||||
- Build command `npm run build`.
|
|
||||||
- Start command `npm run start`.
|
|
||||||
- Port `3000`.
|
|
||||||
- Env değerleri girildi.
|
|
||||||
- Domain ve SSL ayarlandı.
|
|
||||||
- Supabase Auth URL ayarları yapıldı.
|
|
||||||
- `/api/health` endpoint'i çalışıyor.
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
---
|
|
||||||
title: Dokploy ile Deploy
|
|
||||||
description: Neta'yı Dokploy üzerinde Docker Compose olmadan yayınlama.
|
|
||||||
order: 8
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Dokploy ile Deploy
|
|
||||||
|
|
||||||
> Arşiv: Aşağıdaki harici Supabase/ephemeral akış v2'ye aittir. v3 için Dockerfile, tek replica ve `/app/data` persistent volume sözleşmesini `docs/self-hosted-redesign/phase-8-import-release.md` belgesinden uygulayın.
|
|
||||||
|
|
||||||
Dokploy üzerinde Neta'yı yayınlarken artık özel Dockerfile veya Compose dosyasına ihtiyaç yok. Bu projeyi standart bir Next.js repo deploy'u gibi ele almak yeterli.
|
|
||||||
|
|
||||||
Benim önerdiğim yapı şu: Dokploy sadece web uygulamasını çalıştırır, Supabase ise dışarıdaki hazır proje olarak kullanılır.
|
|
||||||
|
|
||||||
## Gerekli Hazırlık
|
|
||||||
|
|
||||||
Başlamadan önce şunlar hazır olmalı:
|
|
||||||
|
|
||||||
- Dokploy kurulu bir VPS.
|
|
||||||
- GitHub üzerinde Neta reposu.
|
|
||||||
- Supabase projesi.
|
|
||||||
- Supabase schema ve storage yapısı.
|
|
||||||
- Ortam değişkenleri.
|
|
||||||
|
|
||||||
Supabase tarafı hazır değilse önce `supabase/setup.sql` dosyasını çalıştırın. Terminalden tek komutla kurmak için:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
psql "postgresql://postgres:[PASSWORD]@[HOST]:5432/postgres" -v ON_ERROR_STOP=1 -f supabase/setup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
SQL Editor kullanıyorsanız `supabase/setup.sql` içeriğini komple yapıştırıp çalıştırabilirsiniz.
|
|
||||||
|
|
||||||
## Dokploy'da Uygulama Oluşturma
|
|
||||||
|
|
||||||
Dokploy içinde yeni bir uygulama oluştururken GitHub reposunu seçin. Uygulamayı Node.js/Next.js app olarak yapılandırın.
|
|
||||||
|
|
||||||
Önerilen komutlar:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
npm run start
|
|
||||||
```
|
|
||||||
|
|
||||||
Docker Compose dosyası seçmeyin. Bu repo artık Supabase'i veya Postgres'i container olarak yanında getirmez.
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
Dokploy environment bölümüne şu değerleri girin:
|
|
||||||
|
|
||||||
```env
|
|
||||||
NEXT_PUBLIC_SITE_URL=https://neta.example.com
|
|
||||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
|
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
||||||
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
|
||||||
```
|
|
||||||
|
|
||||||
Burada en dikkat edilmesi gereken değer `SUPABASE_SERVICE_ROLE_KEY`. Bu key server tarafında kalmalıdır.
|
|
||||||
|
|
||||||
## Domain ve Port
|
|
||||||
|
|
||||||
Next.js production server varsayılan olarak `3000` portunda çalışır.
|
|
||||||
|
|
||||||
Dokploy tarafında:
|
|
||||||
|
|
||||||
- Internal port: `3000`
|
|
||||||
- Domain: kendi domaininiz
|
|
||||||
- SSL: Dokploy üzerinden aktif
|
|
||||||
|
|
||||||
şeklinde ayarlanabilir.
|
|
||||||
|
|
||||||
## Supabase Ayarları
|
|
||||||
|
|
||||||
Supabase Auth tarafında domain ayarını güncelleyin:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Authentication -> URL Configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
Örnek:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Site URL: https://neta.example.com
|
|
||||||
Redirect URLs:
|
|
||||||
https://neta.example.com/**
|
|
||||||
```
|
|
||||||
|
|
||||||
## İlk Çalıştırma
|
|
||||||
|
|
||||||
Deploy tamamlandıktan sonra şu sırayla ilerleyin:
|
|
||||||
|
|
||||||
1. `/api/health` endpoint'ini açın.
|
|
||||||
2. Ana sayfaya girin.
|
|
||||||
3. `/register` üzerinden ilk admin hesabını oluşturun.
|
|
||||||
4. Login olun.
|
|
||||||
5. Müşteri, proje ve görev ekleyerek Supabase bağlantısını test edin.
|
|
||||||
|
|
||||||
## Dokploy'da Nelere Dikkat Edilmeli?
|
|
||||||
|
|
||||||
### Dockerfile Aramayın
|
|
||||||
|
|
||||||
Bu repo artık Dockerfile içermez. Dokploy'un Node.js/Next.js build akışını kullanmak daha doğru.
|
|
||||||
|
|
||||||
### Supabase Container Beklemeyin
|
|
||||||
|
|
||||||
Bu deployment Supabase'i ayağa kaldırmaz. Supabase dışarıda hazır olmalıdır.
|
|
||||||
|
|
||||||
### Env Değerleri Deploy'dan Önce Girilmeli
|
|
||||||
|
|
||||||
Build bazı env değerlerini okuyabilir. Bu yüzden env değerlerini deploy'dan sonra değil, deploy öncesinde eklemek daha sağlıklı olur.
|
|
||||||
|
|
||||||
### RLS Hataları Sessiz Görünebilir
|
|
||||||
|
|
||||||
Supabase policy izin vermediğinde uygulama bazen hata göstermek yerine boş liste gösterebilir. Veri görünmüyorsa önce RLS policy'lerini kontrol edin.
|
|
||||||
|
|
||||||
## Dokploy İçin Kısa Kontrol Listesi
|
|
||||||
|
|
||||||
- GitHub repo bağlandı.
|
|
||||||
- Uygulama Node.js/Next.js olarak oluşturuldu.
|
|
||||||
- Dockerfile/Compose kullanılmadı.
|
|
||||||
- Install command `npm install`.
|
|
||||||
- Build command `npm run build`.
|
|
||||||
- Start command `npm run start`.
|
|
||||||
- Port `3000`.
|
|
||||||
- Env değerleri girildi.
|
|
||||||
- Domain ve SSL aktif.
|
|
||||||
- Supabase Auth URL ayarları yapıldı.
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
---
|
|
||||||
title: İlk Admin Kurulumu
|
|
||||||
description: Neta'da ilk yönetici hesabının nasıl oluşturulduğu.
|
|
||||||
order: 9
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# İlk Admin Kurulumu
|
|
||||||
|
|
||||||
> Arşiv: Aşağıdaki Supabase Auth kurulumu v2'ye aittir. v3 ilk owner hesabını `/register` üzerinden Better Auth + SQLite transaction'ıyla oluşturur; güncel akış repository `README.md` dosyasındadır.
|
|
||||||
|
|
||||||
Neta'yı kişisel bir freelancer çalışma alanı olarak düşündüğüm için sistemde herkesin serbestçe kayıt olmasını istemedim. Bu yüzden ilk admin oluşturulduktan sonra public kayıt akışı kapanır.
|
|
||||||
|
|
||||||
İlk kurulumun temel akışı şudur:
|
|
||||||
|
|
||||||
1. Uygulama Supabase'e bağlanır.
|
|
||||||
2. `/register` sayfası açılır.
|
|
||||||
3. İlk freelancer/admin hesabı oluşturulur.
|
|
||||||
4. `profiles` tablosunda ilk profil oluşur.
|
|
||||||
5. Sonraki public kayıt girişimleri engellenir.
|
|
||||||
|
|
||||||
## İlk Admin Nasıl Oluşturulur?
|
|
||||||
|
|
||||||
Deploy tamamlandıktan sonra şu adrese gidin:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/register
|
|
||||||
```
|
|
||||||
|
|
||||||
E-posta ve şifre girerek ilk hesabı oluşturun. Bu hesap freelancer/admin olarak kabul edilir.
|
|
||||||
|
|
||||||
Kayıt sırasında server tarafında service role kullanılır. Bu yüzden `SUPABASE_SERVICE_ROLE_KEY` ortam değişkeni doğru olmalıdır.
|
|
||||||
|
|
||||||
## Kayıt Neden Kapanıyor?
|
|
||||||
|
|
||||||
Neta'nın mevcut rol modeli şu şekilde:
|
|
||||||
|
|
||||||
- `freelancer`: Ana kullanıcı.
|
|
||||||
- `client`: Müşteri portal kullanıcısı.
|
|
||||||
|
|
||||||
Ana freelancer hesabı public kayıtla yalnızca ilk kez oluşturulur. Bundan sonra sistem herkesin kayıt olmasına izin vermez. Çünkü bu proje çok kullanıcılı açık bir SaaS olarak değil, kişisel self-host çalışma alanı olarak tasarlandı.
|
|
||||||
|
|
||||||
## Setup Kontrolü Nasıl Yapılır?
|
|
||||||
|
|
||||||
Uygulama ilk admin var mı diye Supabase tarafında şu function'ı kullanır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
is_first_admin_setup_available
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu function genellikle `profiles` tablosunda kayıt var mı diye bakar.
|
|
||||||
|
|
||||||
Eğer function Supabase tarafında eksikse veya PostgREST schema cache henüz güncel değilse fallback kontrol devreye girebilir. Bunun için service role key gerekir.
|
|
||||||
|
|
||||||
## İlk Admin Oluşturulamıyorsa
|
|
||||||
|
|
||||||
Şunları kontrol edin:
|
|
||||||
|
|
||||||
- `SUPABASE_SERVICE_ROLE_KEY` doğru mu?
|
|
||||||
- Supabase URL doğru projeyi mi gösteriyor?
|
|
||||||
- `profiles` tablosu var mı?
|
|
||||||
- `request_internal_auth_creation` function'ı var mı?
|
|
||||||
- Auth trigger yeni kullanıcı için profile oluşturuyor mu?
|
|
||||||
- Supabase SQL schema güncel mi?
|
|
||||||
|
|
||||||
## Yanlış Kullanıcı İlk Admin Olduysa
|
|
||||||
|
|
||||||
Bu durumda dikkatli ilerlemek gerekir. Çünkü Supabase Auth ve public tablolar birlikte çalışır.
|
|
||||||
|
|
||||||
Kontrol edilecek yerler:
|
|
||||||
|
|
||||||
- Supabase Auth users.
|
|
||||||
- `profiles` tablosu.
|
|
||||||
- Kullanıcının `role` alanı.
|
|
||||||
|
|
||||||
Geliştirme ortamında yanlış ilk admin oluştuysa ilgili auth user ve profile kaydı silinip tekrar denenebilir. Production ortamında bunu yapmadan önce database backup almak daha güvenlidir.
|
|
||||||
|
|
||||||
## Müşteri Kullanıcıları Nasıl Oluşturulur?
|
|
||||||
|
|
||||||
Müşteri kullanıcıları public `/register` üzerinden oluşturulmaz. Freelancer paneli içinden bir müşteri kaydına portal hesabı açılır.
|
|
||||||
|
|
||||||
Bu işlem sonucunda:
|
|
||||||
|
|
||||||
- Supabase Auth içinde yeni client user oluşur.
|
|
||||||
- `profiles.role` değeri `client` olur.
|
|
||||||
- `clients.client_auth_id` bu auth user id'sine bağlanır.
|
|
||||||
|
|
||||||
Müşteri login olduğunda dashboard'a değil, `/portal` alanına yönlendirilir.
|
|
||||||
|
|
||||||
## Güvenlik Notu
|
|
||||||
|
|
||||||
İlk admin kurulumu tamamlandıktan sonra `/register` linki görünse bile kayıt denemesi server tarafında tekrar kontrol edilir. Yani sadece linki saklamak güvenlik önlemi değildir; asıl kontrol server action ve Supabase tarafındadır.
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
---
|
|
||||||
title: AI Asistan
|
|
||||||
description: Neta'nın AI sohbet ve analiz özelliklerinin nasıl çalıştığı.
|
|
||||||
order: 10
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# AI Asistan
|
|
||||||
|
|
||||||
> Arşiv: Bu belge v2 veri erişimini anlatır. v3 AI provider ve context katmanı server-side çalışır; secret browser'a dönmez. Güncel teknik kayıt `docs/self-hosted-redesign/phase-7-ai-business-backend.md` dosyasındadır.
|
|
||||||
|
|
||||||
Neta'daki AI asistanı, uygulamanın içindeki veriler hakkında hızlıca soru sorabilmek için ekledim. Bu asistanın amacı genel bir chatbot olmak değil; görevler, projeler, finans kayıtları ve günlük notları üzerinden bana bağlama dayalı cevap verebilmesi.
|
|
||||||
|
|
||||||
## Chat Ekranı
|
|
||||||
|
|
||||||
AI sohbet ekranı şu route altında bulunur:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/chat
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu ekranda konuşmalar session olarak tutulur.
|
|
||||||
|
|
||||||
Kullanılan tablolar:
|
|
||||||
|
|
||||||
- `chat_sessions`
|
|
||||||
- `chat_messages`
|
|
||||||
|
|
||||||
Her yeni sohbet için `chat_sessions` içinde bir kayıt açılır. Kullanıcı ve asistan mesajları `chat_messages` tablosunda saklanır.
|
|
||||||
|
|
||||||
## API Route
|
|
||||||
|
|
||||||
Chat yanıtları şu endpoint üzerinden üretilir:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/api/chat
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu endpoint önce kullanıcıyı Supabase session üzerinden doğrular. Sonra kullanıcının son verilerinden kısa bir bağlam oluşturur.
|
|
||||||
|
|
||||||
Bağlama dahil edilen örnek veriler:
|
|
||||||
|
|
||||||
- Son görevler.
|
|
||||||
- Son projeler.
|
|
||||||
- Son 30 gün finans kayıtları.
|
|
||||||
- Son günlük kayıtlar.
|
|
||||||
|
|
||||||
Bu bağlam sistem mesajı içine eklenir. Böylece AI model sadece boş sohbet etmiyor, Neta içindeki güncel kayıtları da dikkate alıyor.
|
|
||||||
|
|
||||||
## AI Provider Ayarları
|
|
||||||
|
|
||||||
AI provider bilgisi `app_settings` tablosunda tutulur.
|
|
||||||
|
|
||||||
Desteklenen provider'lar:
|
|
||||||
|
|
||||||
- OpenAI
|
|
||||||
- Google Gemini
|
|
||||||
- Groq
|
|
||||||
- Ollama
|
|
||||||
|
|
||||||
Web uygulamasındaki ana AI chat akışı cloud provider'lara göre kurgulanmıştır. Ollama seçeneği schema'da ve bazı yardımcı yapılarda geçer, ama local Ollama kullanımının asıl pratiği `main.pyw` gibi masaüstü yardımcı script tarafındadır.
|
|
||||||
|
|
||||||
## Ayarlar Ekranı
|
|
||||||
|
|
||||||
AI ayarları uygulama içinde şu ekrandan yönetilir:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/settings
|
|
||||||
```
|
|
||||||
|
|
||||||
Buradan provider ve API key kaydedilir.
|
|
||||||
|
|
||||||
Kaydedilen alanlar:
|
|
||||||
|
|
||||||
- `ai_provider`
|
|
||||||
- `ai_model`
|
|
||||||
- `api_key`
|
|
||||||
|
|
||||||
Bu değerler `app_settings` tablosunda saklanır.
|
|
||||||
|
|
||||||
## Finans Analizi
|
|
||||||
|
|
||||||
Finans analizi için ayrı bir endpoint vardır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/api/finance-analysis
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu endpoint son 30 günün finans işlemlerini alır, gelir-gider toplamlarını hesaplar ve AI modelden kısa bir finansal yorum ister.
|
|
||||||
|
|
||||||
Bu özellik muhasebe tavsiyesi vermek için değil, freelancer olarak finansal gidişatı hızlıca yorumlamak için eklenmiştir.
|
|
||||||
|
|
||||||
## Proje Risk Analizi
|
|
||||||
|
|
||||||
Proje risk analizi endpoint'i:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/api/project-risk
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu endpoint tek bir proje veya aktif projeler üzerinden risk ve durum raporu üretir.
|
|
||||||
|
|
||||||
Kullanılan bilgiler:
|
|
||||||
|
|
||||||
- Proje adı.
|
|
||||||
- Müşteri adı.
|
|
||||||
- Durum.
|
|
||||||
- Bütçe.
|
|
||||||
- İlerleme.
|
|
||||||
- Başlangıç ve bitiş tarihleri.
|
|
||||||
- Görev sayısı.
|
|
||||||
- Tamamlanan görev sayısı.
|
|
||||||
|
|
||||||
## Embedding ve pgvector
|
|
||||||
|
|
||||||
Projede `document_embeddings` tablosu ve `match_documents` function'ı için altyapı bulunur. Bu yapı, ileride daha gelişmiş RAG özellikleri için kullanılabilir.
|
|
||||||
|
|
||||||
İlgili yardımcı fonksiyonlar:
|
|
||||||
|
|
||||||
- `generateEmbedding`
|
|
||||||
- `saveDocumentEmbedding`
|
|
||||||
- `searchSimilarDocuments`
|
|
||||||
|
|
||||||
Bu yapı tam ürün akışının merkezinde değil, ama gelecekte döküman, not veya proje içeriği üzerinden daha güçlü arama ve bağlam üretme için hazır tutulur.
|
|
||||||
|
|
||||||
## Local Python/Ollama Yardımcısı
|
|
||||||
|
|
||||||
Repo içinde `main.pyw` dosyası da bulunur. Bu dosya web uygulamasından bağımsız bir masaüstü metin işleme yardımcısıdır.
|
|
||||||
|
|
||||||
Ne yapar?
|
|
||||||
|
|
||||||
- Seçili metni kopyalar.
|
|
||||||
- F8 ile menü açar.
|
|
||||||
- Ollama API üzerinden metin düzeltme, çeviri, özetleme gibi işler yapar.
|
|
||||||
|
|
||||||
Bu dosya Neta web deployment akışının parçası değildir. Vercel, Coolify veya Dokploy deploy'u için gerekli değildir. Ama lokal kullanım için korunur.
|
|
||||||
|
|
||||||
## AI İçin Dikkat Edilecekler
|
|
||||||
|
|
||||||
- API key'ler doğru provider'a ait olmalıdır.
|
|
||||||
- Provider seçimi ile model adı uyumlu olmalıdır.
|
|
||||||
- Kullanıcı verisi AI modele gönderildiği için hangi provider'ın kullanıldığı bilinçli seçilmelidir.
|
|
||||||
- Finansal, hukuki veya klinik kesin hüküm üretmemesi için sistem prompt'larında sınırlayıcı ifadeler kullanılmalıdır.
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
---
|
|
||||||
title: Client Portal
|
|
||||||
description: Müşteri portalının amacı, erişim modeli ve kullanım akışı.
|
|
||||||
order: 11
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Client Portal
|
|
||||||
|
|
||||||
> Arşiv: Bu belge v2 portalını anlatır. Güncel Better Auth/SQLite portal sözleşmesi `docs/self-hosted-redesign/phase-6-portal-backend.md` dosyasındadır.
|
|
||||||
|
|
||||||
Client portal, müşteriye sınırlı ve güvenli bir görünüm sunmak için var. Neta'nın ana dashboard'u freelancer içindir. Müşteriye bu dashboard'u açmak doğru olmaz. Bunun yerine müşteri sadece kendisini ilgilendiren proje, görev ve revizyon bilgilerini görür.
|
|
||||||
|
|
||||||
## Portalın Amacı
|
|
||||||
|
|
||||||
Müşteri portalı şu sorulara cevap verir:
|
|
||||||
|
|
||||||
- Projem hangi aşamada?
|
|
||||||
- Genel ilerleme ne durumda?
|
|
||||||
- Hangi görevler tamamlandı veya devam ediyor?
|
|
||||||
- Revizyon talebi gönderebilir miyim?
|
|
||||||
- Proje teslim tarihi ne?
|
|
||||||
|
|
||||||
Bu sayede müşteriyle sürekli manuel durum güncellemesi paylaşmak yerine, kontrollü bir şeffaflık sağlanır.
|
|
||||||
|
|
||||||
## Roller
|
|
||||||
|
|
||||||
Neta'da iki temel rol vardır:
|
|
||||||
|
|
||||||
- `freelancer`
|
|
||||||
- `client`
|
|
||||||
|
|
||||||
Freelancer ana paneli kullanır. Client ise portal alanını kullanır.
|
|
||||||
|
|
||||||
Rol bilgisi `profiles.role` alanında tutulur.
|
|
||||||
|
|
||||||
## Client Auth Bağlantısı
|
|
||||||
|
|
||||||
Müşteri portal erişimi için `clients` tablosunda şu alan kullanılır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
client_auth_id
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu alan Supabase Auth içindeki müşteri kullanıcısının id değerini tutar.
|
|
||||||
|
|
||||||
İlişki şu şekildedir:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
auth.users.id -> clients.client_auth_id -> projects.client_id
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu bağlantı sayesinde müşteri sadece kendi client kaydına bağlı projeleri görebilir.
|
|
||||||
|
|
||||||
## Müşteri Hesabı Oluşturma
|
|
||||||
|
|
||||||
Müşteri public register ekranından kayıt olmaz. Freelancer paneli içinden bir müşteri kaydına portal hesabı oluşturulur.
|
|
||||||
|
|
||||||
Bu işlem sırasında:
|
|
||||||
|
|
||||||
1. Service role ile Supabase Auth user oluşturulur.
|
|
||||||
2. Kullanıcının profile rolü `client` yapılır.
|
|
||||||
3. `clients.client_auth_id` alanı yeni kullanıcıya bağlanır.
|
|
||||||
|
|
||||||
Bu akış için `SUPABASE_SERVICE_ROLE_KEY` gereklidir.
|
|
||||||
|
|
||||||
## Portal Route'ları
|
|
||||||
|
|
||||||
Portal route'ları `app/portal` altında durur.
|
|
||||||
|
|
||||||
Ana ekranlar:
|
|
||||||
|
|
||||||
- `/portal`
|
|
||||||
- `/portal/projects`
|
|
||||||
- `/portal/projects/[id]`
|
|
||||||
- `/portal/tasks`
|
|
||||||
- `/portal/revisions`
|
|
||||||
|
|
||||||
Portal layout, kullanıcının rolünü kontrol eder. Rol `client` değilse kullanıcı dashboard tarafına yönlendirilir.
|
|
||||||
|
|
||||||
## Müşteri Neleri Görebilir?
|
|
||||||
|
|
||||||
Müşteri şunları görebilir:
|
|
||||||
|
|
||||||
- Kendisine bağlı projeler.
|
|
||||||
- Proje adı, açıklama, durum, ilerleme ve teslim tarihi.
|
|
||||||
- Public olarak işaretlenmiş görevler.
|
|
||||||
- Proje planlama bölümleri.
|
|
||||||
- Kendi revizyon talepleri.
|
|
||||||
|
|
||||||
Müşteri şunları görmemelidir:
|
|
||||||
|
|
||||||
- Freelancer dashboard'u.
|
|
||||||
- Finans kayıtları.
|
|
||||||
- Diğer müşteriler.
|
|
||||||
- Diğer projeler.
|
|
||||||
- Private görevler.
|
|
||||||
- App settings.
|
|
||||||
- AI chat geçmişi.
|
|
||||||
|
|
||||||
## Public Task Mantığı
|
|
||||||
|
|
||||||
Görevlerde şu alan bulunur:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
is_public_to_client
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu alan `true` ise müşteri portalında gösterilebilir. Böylece iç operasyon görevleri müşteriyle paylaşılmadan tutulabilir.
|
|
||||||
|
|
||||||
## Revizyon Talepleri
|
|
||||||
|
|
||||||
Müşteri proje detay ekranından revizyon talebi oluşturabilir.
|
|
||||||
|
|
||||||
Revizyon durumları:
|
|
||||||
|
|
||||||
- `pending`
|
|
||||||
- `in_progress`
|
|
||||||
- `completed`
|
|
||||||
- `rejected`
|
|
||||||
|
|
||||||
Freelancer dashboard tarafında bu talepler görülebilir ve durumları güncellenebilir.
|
|
||||||
|
|
||||||
## Güvenlik Modeli
|
|
||||||
|
|
||||||
Portal güvenliği iki katmanda sağlanır:
|
|
||||||
|
|
||||||
1. Next.js layout seviyesinde rol kontrolü.
|
|
||||||
2. Supabase RLS policy seviyesinde veri erişim kontrolü.
|
|
||||||
|
|
||||||
Sadece layout kontrolüne güvenmek doğru değildir. Asıl kritik nokta Supabase policy'lerinin doğru olmasıdır.
|
|
||||||
|
|
||||||
## Portalı Özelleştirme
|
|
||||||
|
|
||||||
Portal menüsü şu dosyadan yönetilir:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
config/portal-sidebar.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Portal shell bileşeni:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
components/layout/portal-shell.tsx
|
|
||||||
```
|
|
||||||
|
|
||||||
Müşteriye daha fazla bilgi göstermek isterseniz önce RLS politikasını, sonra server component veri sorgusunu, en son UI bileşenini güncellemek gerekir.
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
---
|
|
||||||
title: Storage ve Dosyalar
|
|
||||||
description: Avatar ve proje görselleri için Supabase Storage kullanımı.
|
|
||||||
order: 12
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Storage ve Dosyalar
|
|
||||||
|
|
||||||
> Arşiv: Aşağıdaki Supabase Storage akışı v2'ye aittir. v3 dosyaları `DATA_DIR/uploads` altında ve checksum'lı metadata ile tutar; güncel kayıt `docs/self-hosted-redesign/phase-3-storage-branding.md` dosyasındadır.
|
|
||||||
|
|
||||||
Neta'da dosya saklama için Supabase Storage kullanılır. Şu an iki temel dosya ihtiyacı var: profil fotoğrafları ve proje kapak görselleri.
|
|
||||||
|
|
||||||
Bu dosyaları uygulama reposu içinde tutmuyorum. Çünkü production ortamında dosya upload'larının kalıcı ve güvenli bir storage alanında durması gerekir. Supabase Storage bu iş için yeterli ve pratik bir çözüm sağlıyor.
|
|
||||||
|
|
||||||
## Gerekli Bucket'lar
|
|
||||||
|
|
||||||
Neta iki bucket bekler:
|
|
||||||
|
|
||||||
| Bucket | Public | Kullanım |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `avatars` | Evet | Profil fotoğrafları |
|
|
||||||
| `project-assets` | Hayır | Proje kapak görselleri |
|
|
||||||
|
|
||||||
## `avatars` Bucket
|
|
||||||
|
|
||||||
Profil fotoğrafları için kullanılır.
|
|
||||||
|
|
||||||
Önerilen ayar:
|
|
||||||
|
|
||||||
- Bucket adı: `avatars`
|
|
||||||
- Public: Evet
|
|
||||||
- MIME kısıtı: Gerekirse image türleriyle sınırlandırılabilir
|
|
||||||
|
|
||||||
Avatar görselleri public URL ile gösterilir. Bu yüzden bucket public olabilir.
|
|
||||||
|
|
||||||
Profil fotoğrafı upload işlemi Ayarlar ekranından yapılır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/settings
|
|
||||||
```
|
|
||||||
|
|
||||||
Server action tarafında service role client kullanılır ve upload sonrası public URL profile kaydına yazılır.
|
|
||||||
|
|
||||||
## `project-assets` Bucket
|
|
||||||
|
|
||||||
Proje kapak görselleri için kullanılır.
|
|
||||||
|
|
||||||
Önerilen ayar:
|
|
||||||
|
|
||||||
- Bucket adı: `project-assets`
|
|
||||||
- Public: Hayır
|
|
||||||
- File size limit: 5 MB
|
|
||||||
- Allowed MIME types:
|
|
||||||
- `image/jpeg`
|
|
||||||
- `image/png`
|
|
||||||
- `image/webp`
|
|
||||||
- `image/gif`
|
|
||||||
|
|
||||||
Bu bucket private kalmalıdır. Proje detay sayfasında görsel gerektiğinde server tarafında signed URL üretilir.
|
|
||||||
|
|
||||||
## Dosya Yolları
|
|
||||||
|
|
||||||
Proje görselleri şu mantıkla saklanır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
{user_id}/projects/{project_id}/{file_name}
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu yapı storage policy yazmayı kolaylaştırır. Çünkü dosya yolunun ilk segmenti kullanıcının id değeridir.
|
|
||||||
|
|
||||||
## Signed URL Kullanımı
|
|
||||||
|
|
||||||
Private bucket içindeki proje görselleri doğrudan public URL ile gösterilmez. Bunun yerine server tarafında kısa süreli signed URL üretilir.
|
|
||||||
|
|
||||||
Bu yaklaşım şu avantajları sağlar:
|
|
||||||
|
|
||||||
- Proje dosyaları herkese açık olmaz.
|
|
||||||
- URL süresi sınırlıdır.
|
|
||||||
- Yetkisiz kullanıcılar dosya yolunu bilse bile doğrudan erişemez.
|
|
||||||
|
|
||||||
## Service Role Neden Kullanılıyor?
|
|
||||||
|
|
||||||
Upload işlemlerinde service role client kullanılır. Bunun sebebi storage policy karmaşıklığını azaltmak ve server-side işlemleri güvenli tutmaktır.
|
|
||||||
|
|
||||||
Yine de service role key sadece server tarafında kalmalıdır.
|
|
||||||
|
|
||||||
## Storage Policy Mantığı
|
|
||||||
|
|
||||||
Policy'ler şu iki durumu desteklemelidir:
|
|
||||||
|
|
||||||
- Kullanıcı kendi klasöründeki dosyaları yönetebilmeli.
|
|
||||||
- Service role server-side işlemler için erişebilmeli.
|
|
||||||
|
|
||||||
`project-assets` için tipik yol kontrolü:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
auth.uid()::text = storage.foldername(name)[1]
|
|
||||||
```
|
|
||||||
|
|
||||||
Service role için ayrıca bypass izni verilmelidir.
|
|
||||||
|
|
||||||
## Sık Karşılaşılan Hatalar
|
|
||||||
|
|
||||||
### Upload Başarısız
|
|
||||||
|
|
||||||
Kontrol edilecekler:
|
|
||||||
|
|
||||||
- Bucket adı doğru mu?
|
|
||||||
- Bucket var mı?
|
|
||||||
- MIME type izinli mi?
|
|
||||||
- Dosya boyutu limiti aşılıyor mu?
|
|
||||||
- Service role key doğru mu?
|
|
||||||
|
|
||||||
### Görsel Yükleniyor Ama Görünmüyor
|
|
||||||
|
|
||||||
`project-assets` private olduğu için public URL beklemeyin. Signed URL üretimi çalışıyor mu kontrol edin.
|
|
||||||
|
|
||||||
### Avatar Görünmüyor
|
|
||||||
|
|
||||||
`avatars` bucket public değilse public URL görüntülenmeyebilir. Ya bucket public yapılmalı ya da avatar için de signed URL yaklaşımı kullanılmalıdır.
|
|
||||||
|
|
||||||
## Production Notu
|
|
||||||
|
|
||||||
Storage ayarları deploy platformundan bağımsızdır. Vercel, Coolify veya Dokploy fark etmez; dosya tarafındaki davranışı Supabase bucket ve policy ayarları belirler.
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
---
|
|
||||||
title: Geliştirme
|
|
||||||
description: Neta üzerinde lokal geliştirme yapma ve kod yapısını anlama.
|
|
||||||
order: 13
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Geliştirme
|
|
||||||
|
|
||||||
> Arşiv: Aşağıdaki Supabase geliştirme akışı v2'ye aittir. Güncel lokal kurulum ve kalite komutları repository `README.md` dosyasındadır.
|
|
||||||
|
|
||||||
Neta'yı geliştirmek için önce standart bir Next.js uygulaması gibi düşünmek yeterli. Uygulama Supabase'e dışarıdan bağlanır; local geliştirme sırasında da aynı şekilde bir Supabase projesine ihtiyaç vardır.
|
|
||||||
|
|
||||||
## Kurulum
|
|
||||||
|
|
||||||
Önce bağımlılıkları kurun:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Sonra `.env.example` dosyasını `.env.local` olarak kopyalayıp değerleri doldurun:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.example .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
Windows PowerShell için:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
Copy-Item .env.example .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
Dev server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Uygulama varsayılan olarak şurada açılır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
http://localhost:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
## Klasör Yapısı
|
|
||||||
|
|
||||||
Ana klasörler:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
app/
|
|
||||||
components/
|
|
||||||
config/
|
|
||||||
hooks/
|
|
||||||
lib/
|
|
||||||
public/
|
|
||||||
docs/
|
|
||||||
supabase/
|
|
||||||
```
|
|
||||||
|
|
||||||
## `app/`
|
|
||||||
|
|
||||||
Next.js App Router route'ları burada durur.
|
|
||||||
|
|
||||||
Önemli alanlar:
|
|
||||||
|
|
||||||
- `app/(dashboard)`: Freelancer dashboard ekranları.
|
|
||||||
- `app/portal`: Müşteri portalı.
|
|
||||||
- `app/api`: API route'ları.
|
|
||||||
- `app/login`: Login ekranı.
|
|
||||||
- `app/register`: İlk admin kayıt ekranı.
|
|
||||||
|
|
||||||
## `components/`
|
|
||||||
|
|
||||||
Paylaşılan UI bileşenleri burada durur.
|
|
||||||
|
|
||||||
Örnekler:
|
|
||||||
|
|
||||||
- Layout shell bileşenleri.
|
|
||||||
- Auth bileşenleri.
|
|
||||||
- UI yardımcıları.
|
|
||||||
- Dashboard hızlı aksiyon bileşenleri.
|
|
||||||
|
|
||||||
## `config/`
|
|
||||||
|
|
||||||
Menü ve navigation gibi sabit konfigürasyonlar burada durur.
|
|
||||||
|
|
||||||
Önemli dosyalar:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
config/sidebar.ts
|
|
||||||
config/portal-sidebar.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Yeni bir dashboard sayfası eklerken genelde `sidebar.ts` dosyasını da güncellemek gerekir.
|
|
||||||
|
|
||||||
## `lib/`
|
|
||||||
|
|
||||||
Uygulama yardımcıları ve servis bağlantıları burada durur.
|
|
||||||
|
|
||||||
Önemli dosyalar:
|
|
||||||
|
|
||||||
- `lib/supabase/client.ts`
|
|
||||||
- `lib/supabase/server.ts`
|
|
||||||
- `lib/supabase/admin.ts`
|
|
||||||
- `lib/auth/first-admin-setup.ts`
|
|
||||||
- `lib/auth/internal-users.ts`
|
|
||||||
- `lib/ai.ts`
|
|
||||||
- `lib/ai/embeddings.ts`
|
|
||||||
|
|
||||||
## Veri Çekme Deseni
|
|
||||||
|
|
||||||
Projede sık kullandığım desen şu:
|
|
||||||
|
|
||||||
1. `page.tsx` server component olarak Supabase'ten veriyi çeker.
|
|
||||||
2. Client tarafında etkileşim gerekiyorsa veri bir `*-client.tsx` bileşenine aktarılır.
|
|
||||||
3. Form işlemleri `actions.ts` içinde server action olarak yapılır.
|
|
||||||
|
|
||||||
Örnek:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
app/(dashboard)/tasks/page.tsx
|
|
||||||
app/(dashboard)/tasks/tasks-client.tsx
|
|
||||||
app/(dashboard)/tasks/actions.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu desen hem okunabilirliği artırır hem de Supabase sorgularının nerede çalıştığını netleştirir.
|
|
||||||
|
|
||||||
## Yeni Modül Eklemek
|
|
||||||
|
|
||||||
Yeni bir modül eklerken şu sırayı izlemek mantıklı:
|
|
||||||
|
|
||||||
1. Supabase tablosunu ve RLS policy'lerini planla.
|
|
||||||
2. Server page dosyasını oluştur.
|
|
||||||
3. Gerekirse client component oluştur.
|
|
||||||
4. CRUD işlemleri için `actions.ts` ekle.
|
|
||||||
5. Sidebar config'e menü ekle.
|
|
||||||
6. Gerekirse dashboard/analiz ekranına özet veri ekle.
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
Production build kontrolü:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Build başarılıysa TypeScript ve Next.js route üretimi de temel seviyede doğrulanmış olur.
|
|
||||||
|
|
||||||
## Lint
|
|
||||||
|
|
||||||
Lint komutu:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run lint
|
|
||||||
```
|
|
||||||
|
|
||||||
Eğer lint yavaş çalışıyorsa `.next` klasörünün temiz olduğundan emin olun. Generated dosyaların gereksiz taranması geliştirme deneyimini bozabilir.
|
|
||||||
|
|
||||||
## `.next` Cache Temizleme
|
|
||||||
|
|
||||||
Dev server bazen eski route type veya cache nedeniyle garip davranabilir. Bu durumda:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
Remove-Item .next -Recurse -Force
|
|
||||||
```
|
|
||||||
|
|
||||||
Sonra tekrar:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Python Yardımcı Dosyası
|
|
||||||
|
|
||||||
Repo içinde `main.pyw` ve `requirements.txt` bulunur. Bunlar web uygulamasının deploy akışına dahil değildir. Lokal Ollama tabanlı masaüstü metin işleme yardımcısı olarak korunur.
|
|
||||||
|
|
||||||
Python bağımlılıkları web build'i için gerekli değildir.
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
---
|
|
||||||
title: Özelleştirme
|
|
||||||
description: Neta'nın marka, arayüz, modül ve iş akışı açısından nasıl özelleştirileceği.
|
|
||||||
order: 14
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Özelleştirme
|
|
||||||
|
|
||||||
> Arşiv: Bu kod tabanlı v2 özelleştirme rehberidir. v3 instance branding'i SQLite/local file service ve server-rendered Poyraz semantic tokenları üzerinden uygular; güncel özet repository `README.md` dosyasındadır.
|
|
||||||
|
|
||||||
Neta'yı kendi kullanım şeklime göre geliştirdiğim için özelleştirilebilir kalmasına dikkat ettim. Renkler, logo, menüler, modüller, AI prompt'ları ve portal görünümü ihtiyaçlara göre değiştirilebilir.
|
|
||||||
|
|
||||||
## Logo Değiştirme
|
|
||||||
|
|
||||||
Logo dosyaları `public/logo` altında durur.
|
|
||||||
|
|
||||||
Kullanılan dosyalar:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
public/logo/lightLogoLong.png
|
|
||||||
public/logo/blackLogoLong.png
|
|
||||||
public/logo/iconLogo.png
|
|
||||||
```
|
|
||||||
|
|
||||||
Logo kullanılan ana yerler:
|
|
||||||
|
|
||||||
- Auth ekranı.
|
|
||||||
- Dashboard sidebar.
|
|
||||||
- Portal sidebar.
|
|
||||||
- Mobil header.
|
|
||||||
|
|
||||||
Logo değiştirirken aynı dosya adlarını korumak en kolay yoldur. Dosya adlarını değiştirirseniz ilgili `Image` kullanımlarını da güncellemeniz gerekir.
|
|
||||||
|
|
||||||
## Renk ve Tema
|
|
||||||
|
|
||||||
Ana renk değişkenleri `app/globals.css` içinde tanımlanır.
|
|
||||||
|
|
||||||
Özellikle şu CSS değişkenleri önemlidir:
|
|
||||||
|
|
||||||
```css
|
|
||||||
--poyraz-background
|
|
||||||
--poyraz-foreground
|
|
||||||
--poyraz-primary
|
|
||||||
--poyraz-primary-foreground
|
|
||||||
--poyraz-border
|
|
||||||
--poyraz-muted
|
|
||||||
```
|
|
||||||
|
|
||||||
Tailwind tarafındaki eşlemeler `tailwind.config.ts` içinde bulunur.
|
|
||||||
|
|
||||||
Neta şu an açık tema odaklı tasarlanmıştır. Dark mode altyapısı tamamen ürünleşmiş kabul edilmemelidir.
|
|
||||||
|
|
||||||
## Sidebar Menüleri
|
|
||||||
|
|
||||||
Dashboard menüsü:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
config/sidebar.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Portal menüsü:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
config/portal-sidebar.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Yeni bir ekran eklediğinizde önce route'u oluşturun, sonra ilgili sidebar config'e menü elemanı ekleyin.
|
|
||||||
|
|
||||||
Örnek dashboard menü elemanı:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{ title: "Projeler", href: "/projects", icon: FolderKanban }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dashboard Özelleştirme
|
|
||||||
|
|
||||||
Dashboard ekranı iki parçadan oluşur:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
app/(dashboard)/page.tsx
|
|
||||||
app/(dashboard)/dashboard-client.tsx
|
|
||||||
```
|
|
||||||
|
|
||||||
Server component veriyi toplar. Client component KPI ve grafik görünümünü üretir.
|
|
||||||
|
|
||||||
Yeni KPI eklemek için:
|
|
||||||
|
|
||||||
1. `page.tsx` içinde gerekli veriyi çekin.
|
|
||||||
2. `dashboardData` içine ekleyin.
|
|
||||||
3. `DashboardClient` tipini güncelleyin.
|
|
||||||
4. UI içinde yeni kartı oluşturun.
|
|
||||||
|
|
||||||
## Yeni Alan Eklemek
|
|
||||||
|
|
||||||
Bir tabloya yeni field eklemek istediğinizde sadece formu güncellemek yetmez.
|
|
||||||
|
|
||||||
Genel sıra:
|
|
||||||
|
|
||||||
1. Supabase tablosuna field ekle.
|
|
||||||
2. RLS etkisini kontrol et.
|
|
||||||
3. Server query select listesine field ekle.
|
|
||||||
4. TypeScript tiplerini güncelle.
|
|
||||||
5. Form input'unu ekle.
|
|
||||||
6. `actions.ts` payload okuma fonksiyonunu güncelle.
|
|
||||||
7. Liste/detay UI'ında göster.
|
|
||||||
|
|
||||||
Bu sırayı izlemek runtime hatalarını azaltır.
|
|
||||||
|
|
||||||
## AI Prompt Özelleştirme
|
|
||||||
|
|
||||||
AI chat prompt'u şu dosyada bulunur:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
app/api/chat/route.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Finans analizi:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
app/api/finance-analysis/route.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Proje risk analizi:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
app/api/project-risk/route.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Prompt'ları değiştirirken dikkat ettiğim noktalar:
|
|
||||||
|
|
||||||
- Cevap dili Türkçe olmalı.
|
|
||||||
- Gereksiz uzun cevap istenmemeli.
|
|
||||||
- Veri yoksa model bunu açıkça söylemeli.
|
|
||||||
- Finansal, hukuki veya klinik kesin hüküm verilmemeli.
|
|
||||||
|
|
||||||
## Client Portal Özelleştirme
|
|
||||||
|
|
||||||
Portal shell:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
components/layout/portal-shell.tsx
|
|
||||||
```
|
|
||||||
|
|
||||||
Portal sayfaları:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
app/portal
|
|
||||||
```
|
|
||||||
|
|
||||||
Müşteriye yeni bir veri göstermek için sadece UI eklemek yetmez. Supabase RLS policy'sinin de o veriye izin vermesi gerekir.
|
|
||||||
|
|
||||||
Önerilen sıra:
|
|
||||||
|
|
||||||
1. Müşteri gerçekten bu veriyi görmeli mi karar ver.
|
|
||||||
2. RLS policy ekle veya güncelle.
|
|
||||||
3. Portal server component içinde sorguyu yaz.
|
|
||||||
4. Client component içinde göster.
|
|
||||||
|
|
||||||
## Yeni Modül Eklemek
|
|
||||||
|
|
||||||
Yeni modül için örnek klasör yapısı:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
app/(dashboard)/new-module/page.tsx
|
|
||||||
app/(dashboard)/new-module/new-module-client.tsx
|
|
||||||
app/(dashboard)/new-module/actions.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Eğer modül müşteriyle ilişkiliyse `client_id`, projeyle ilişkiliyse `project_id` alanlarını baştan düşünmek iyi olur.
|
|
||||||
|
|
||||||
## Metinleri Türkçeleştirme
|
|
||||||
|
|
||||||
Projede bazı eski dosyalarda karakter bozulmaları veya İngilizce metinler kalmış olabilir. Ürünleştirme sırasında bu metinlerin tek tek düzeltilmesi gerekir.
|
|
||||||
|
|
||||||
Öncelikli yerler:
|
|
||||||
|
|
||||||
- Sidebar başlıkları.
|
|
||||||
- Form label'ları.
|
|
||||||
- Toast mesajları.
|
|
||||||
- Empty state metinleri.
|
|
||||||
- Hata mesajları.
|
|
||||||
|
|
||||||
## Kişisel Kullanıma Göre Sadeleştirme
|
|
||||||
|
|
||||||
Neta'yı herkes aynı şekilde kullanmak zorunda değil. Eğer bazı modüller size gereksizse menüden kaldırabilir, route'ları bırakıp görünürlüğü azaltabilirsiniz.
|
|
||||||
|
|
||||||
Örneğin finans modülünü kullanmayacaksanız:
|
|
||||||
|
|
||||||
- Sidebar'dan kaldırın.
|
|
||||||
- Dashboard finans kartlarını kaldırın.
|
|
||||||
- AI context içinde finans sorgularını sadeleştirin.
|
|
||||||
|
|
||||||
Bu yaklaşım projeyi daha kişisel ve odaklı tutar.
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
---
|
|
||||||
title: Sorun Giderme
|
|
||||||
description: Kurulum, deploy, Supabase ve giriş problemlerinde kontrol edilmesi gerekenler.
|
|
||||||
order: 15
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Sorun Giderme
|
|
||||||
|
|
||||||
> Arşiv: Aşağıdaki Supabase/RLS sorun giderme akışı v2'ye aittir. v3 operasyon kontrolleri ve health endpoint'leri Faz 8 runbook'unda tanımlıdır.
|
|
||||||
|
|
||||||
Neta bir Next.js uygulaması gibi deploy edilse de Supabase, RLS, Auth ve Storage bağlantıları olduğu için sorunları doğru katmanda aramak önemli. Bu sayfada en sık karşılaşılabilecek durumları topladım.
|
|
||||||
|
|
||||||
## Login Sayfası Açılmıyor
|
|
||||||
|
|
||||||
Önce `.next` cache'ini temizleyin:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
Remove-Item .next -Recurse -Force
|
|
||||||
```
|
|
||||||
|
|
||||||
Sonra dev server'ı tekrar başlatın:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Eğer sayfa hâlâ açılmıyorsa şunları kontrol edin:
|
|
||||||
|
|
||||||
- `NEXT_PUBLIC_SUPABASE_URL` dolu mu?
|
|
||||||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY` doğru mu?
|
|
||||||
- Supabase projesi erişilebilir mi?
|
|
||||||
- Dev server loglarında route compile hatası var mı?
|
|
||||||
|
|
||||||
Login sayfası public route olduğu için session kontrolünün sayfayı bloklamaması gerekir.
|
|
||||||
|
|
||||||
## Giriş Yapıyorum Ama Dashboard Açılmıyor
|
|
||||||
|
|
||||||
Kontrol edilecekler:
|
|
||||||
|
|
||||||
- Kullanıcının `profiles` tablosunda kaydı var mı?
|
|
||||||
- `profiles.role` değeri `freelancer` mı?
|
|
||||||
- Kullanıcı yanlışlıkla `client` rolünde mi?
|
|
||||||
- Middleware kullanıcıyı `/portal` tarafına mı yönlendiriyor?
|
|
||||||
|
|
||||||
Client rolündeki kullanıcı dashboard'a değil, portal'a yönlendirilir.
|
|
||||||
|
|
||||||
## İlk Admin Oluşturulamıyor
|
|
||||||
|
|
||||||
Bu genellikle service role veya Supabase function problemidir.
|
|
||||||
|
|
||||||
Kontrol edin:
|
|
||||||
|
|
||||||
- `SUPABASE_SERVICE_ROLE_KEY` var mı?
|
|
||||||
- Service role key doğru Supabase projesine mi ait?
|
|
||||||
- `request_internal_auth_creation` function'ı var mı?
|
|
||||||
- `is_first_admin_setup_available` function'ı var mı?
|
|
||||||
- `profiles` tablosu var mı?
|
|
||||||
- Auth trigger yeni kullanıcı için profile oluşturuyor mu?
|
|
||||||
|
|
||||||
## Register Kapalı Görünüyor
|
|
||||||
|
|
||||||
`profiles` tablosunda herhangi bir kayıt varsa sistem ilk admin kurulmuş kabul eder.
|
|
||||||
|
|
||||||
Supabase SQL editor'da kontrol edebilirsiniz:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
select id, role from public.profiles;
|
|
||||||
```
|
|
||||||
|
|
||||||
Geliştirme ortamında yanlış kayıt oluştuysa auth user ve profile kayıtlarını temizleyip tekrar deneyebilirsiniz.
|
|
||||||
|
|
||||||
## Veri Ekleniyor Ama Listede Görünmüyor
|
|
||||||
|
|
||||||
Bu çoğu zaman RLS kaynaklıdır.
|
|
||||||
|
|
||||||
Kontrol edin:
|
|
||||||
|
|
||||||
- Kayıtta `user_id` doğru mu?
|
|
||||||
- Policy `auth.uid() = user_id` mantığıyla uyumlu mu?
|
|
||||||
- Sorguda `.eq("user_id", user.id)` filtresi var mı?
|
|
||||||
- Kullanıcı session'ı gerçekten var mı?
|
|
||||||
|
|
||||||
Supabase bazen yetkisiz veriyi hata yerine boş sonuç olarak döndürür. Bu yüzden boş listeyi her zaman "veri yok" diye yorumlamamak gerekir.
|
|
||||||
|
|
||||||
## Storage Upload Hata Veriyor
|
|
||||||
|
|
||||||
Kontrol listesi:
|
|
||||||
|
|
||||||
- `avatars` bucket var mı?
|
|
||||||
- `project-assets` bucket var mı?
|
|
||||||
- `project-assets` private mı?
|
|
||||||
- Dosya boyutu 5 MB sınırını aşıyor mu?
|
|
||||||
- MIME type izinli mi?
|
|
||||||
- Storage policy service role erişimine izin veriyor mu?
|
|
||||||
- `SUPABASE_SERVICE_ROLE_KEY` doğru mu?
|
|
||||||
|
|
||||||
## Proje Görseli Yükleniyor Ama Görünmüyor
|
|
||||||
|
|
||||||
`project-assets` private bucket olduğu için public URL beklemeyin. Uygulama signed URL üretir.
|
|
||||||
|
|
||||||
Kontrol edin:
|
|
||||||
|
|
||||||
- `cover_image_path` tabloya yazılmış mı?
|
|
||||||
- Signed URL üretimi hata veriyor mu?
|
|
||||||
- Bucket adı doğru mu?
|
|
||||||
|
|
||||||
## AI Asistan Cevap Vermiyor
|
|
||||||
|
|
||||||
Kontrol edin:
|
|
||||||
|
|
||||||
- Ayarlar ekranında provider seçilmiş mi?
|
|
||||||
- API key kaydedilmiş mi?
|
|
||||||
- `app_settings` tablosunda kullanıcı için kayıt var mı?
|
|
||||||
- Provider ile model adı uyumlu mu?
|
|
||||||
- API key geçerli mi?
|
|
||||||
|
|
||||||
Chat endpoint'i için route:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/api/chat
|
|
||||||
```
|
|
||||||
|
|
||||||
## Finans veya Proje Analizi Hata Veriyor
|
|
||||||
|
|
||||||
Bu endpoint'ler de AI provider ayarlarını kullanır:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/api/finance-analysis
|
|
||||||
/api/project-risk
|
|
||||||
```
|
|
||||||
|
|
||||||
Eğer API key yoksa analiz endpoint'i hata döndürür.
|
|
||||||
|
|
||||||
## Build Hatası
|
|
||||||
|
|
||||||
Önce bağımlılıkları temiz kurmayı deneyin:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Projede npm lockfile kullanılmalıdır. `pnpm-lock.yaml` veya başka lockfile'lar deployment platformunu karıştırabilir.
|
|
||||||
|
|
||||||
## Slow Filesystem Uyarısı
|
|
||||||
|
|
||||||
Next.js dev server bazen Windows veya yavaş disk üzerinde şu tarz uyarı verebilir:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Slow filesystem detected
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu uyarı tek başına hata değildir. Ama dev server çok yavaşsa:
|
|
||||||
|
|
||||||
- Projeyi daha hızlı lokal diske taşıyın.
|
|
||||||
- `.next` klasörünü temizleyin.
|
|
||||||
- Antivirüs taramasının proje klasörünü yavaşlatmadığından emin olun.
|
|
||||||
|
|
||||||
## Health Check
|
|
||||||
|
|
||||||
Uygulamanın temel olarak ayağa kalktığını test etmek için:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
/api/health
|
|
||||||
```
|
|
||||||
|
|
||||||
Beklenen cevap:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"status": "ok"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Bu endpoint Supabase bağlantısını test etmez; sadece Next.js uygulamasının çalıştığını gösterir.
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
---
|
|
||||||
title: Güvenlik Notları
|
|
||||||
description: Neta'yı güvenli şekilde kullanmak için dikkat edilmesi gerekenler.
|
|
||||||
order: 16
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Güvenlik Notları
|
|
||||||
|
|
||||||
> Arşiv: Bu belgenin Supabase/RLS maddeleri v2'ye aittir. Güncel Better Auth, service authorization, filesystem ve secret kararları ana dönüşüm planında tanımlıdır.
|
|
||||||
|
|
||||||
Neta kişisel kullanım ve self-host mantığıyla tasarlandı, ama yine de hassas veriler tutar. Müşteri bilgileri, proje detayları, finans kayıtları, AI API key'leri ve portal erişimleri doğru korunmalıdır.
|
|
||||||
|
|
||||||
Bu sayfada projeyi kullanırken özellikle dikkat ettiğim güvenlik noktalarını topladım.
|
|
||||||
|
|
||||||
## Service Role Key
|
|
||||||
|
|
||||||
`SUPABASE_SERVICE_ROLE_KEY` en hassas ortam değişkenidir. Bu key Supabase RLS kontrollerini bypass edebilir.
|
|
||||||
|
|
||||||
Bu yüzden:
|
|
||||||
|
|
||||||
- Asla browser'a gönderilmemelidir.
|
|
||||||
- Asla `NEXT_PUBLIC_` prefix'iyle tanımlanmamalıdır.
|
|
||||||
- Public repo içine yazılmamalıdır.
|
|
||||||
- `.env.local` commit edilmemelidir.
|
|
||||||
- Sadece deploy platformunun secret/env panelinde saklanmalıdır.
|
|
||||||
|
|
||||||
Bu key Neta'da sadece server tarafında kullanılır.
|
|
||||||
|
|
||||||
## Anon Key Güvenliği
|
|
||||||
|
|
||||||
`NEXT_PUBLIC_SUPABASE_ANON_KEY` public bir key'dir. Browser tarafında görünmesi normaldir.
|
|
||||||
|
|
||||||
Anon key'in güvenliği RLS policy'lerine bağlıdır. Yani anon key sızdı diye panik yapmak yerine, asıl kontrol edilmesi gereken şey Supabase policy'leridir.
|
|
||||||
|
|
||||||
Eğer RLS kapalıysa anon key ile veri erişimi tehlikeli hâle gelir.
|
|
||||||
|
|
||||||
## RLS Her Zaman Açık Olmalı
|
|
||||||
|
|
||||||
Neta'nın güvenlik modeli Supabase RLS üzerine kurulu.
|
|
||||||
|
|
||||||
Özellikle şu tablolarda RLS açık olmalıdır:
|
|
||||||
|
|
||||||
- `profiles`
|
|
||||||
- `clients`
|
|
||||||
- `projects`
|
|
||||||
- `tasks`
|
|
||||||
- `calendar_events`
|
|
||||||
- `finance_transactions`
|
|
||||||
- `daily_logs`
|
|
||||||
- `app_settings`
|
|
||||||
- `chat_sessions`
|
|
||||||
- `chat_messages`
|
|
||||||
- `project_planning_sections`
|
|
||||||
- `project_revisions`
|
|
||||||
- `client_activities`
|
|
||||||
|
|
||||||
Freelancer verileri genelde şu mantıkla korunur:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
auth.uid() = user_id
|
|
||||||
```
|
|
||||||
|
|
||||||
## Client Portal İzolasyonu
|
|
||||||
|
|
||||||
Müşteri portalı açarken en kritik konu veri izolasyonudur.
|
|
||||||
|
|
||||||
Müşteri sadece kendi `client_auth_id` bağlantısına ait verileri görmelidir. Başka müşterilerin projelerini, finans kayıtlarını veya private görevleri görmemelidir.
|
|
||||||
|
|
||||||
Özellikle kontrol edilmesi gerekenler:
|
|
||||||
|
|
||||||
- `clients.client_auth_id`
|
|
||||||
- `projects.client_id`
|
|
||||||
- `tasks.is_public_to_client`
|
|
||||||
- `project_revisions.client_id`
|
|
||||||
|
|
||||||
Portal tarafında UI kontrolü olsa bile asıl güvenlik RLS policy içinde olmalıdır.
|
|
||||||
|
|
||||||
## AI API Key'leri
|
|
||||||
|
|
||||||
AI provider API key'leri `app_settings` tablosunda tutulur.
|
|
||||||
|
|
||||||
Bu pratik kişisel kullanım için sade bir çözüm sağlar, ama production güvenliği açısından şu noktalar önemlidir:
|
|
||||||
|
|
||||||
- `app_settings` tablosunda RLS açık olmalıdır.
|
|
||||||
- Kullanıcı sadece kendi ayarını okuyup yazabilmelidir.
|
|
||||||
- Service role key dışında toplu erişim olmamalıdır.
|
|
||||||
- Gerekiyorsa ileride API key encryption eklenmelidir.
|
|
||||||
|
|
||||||
## AI'ya Gönderilen Veri
|
|
||||||
|
|
||||||
AI asistan, kullanıcının görev, proje, finans ve günlük kayıtlarından bağlam üretir. Bu veri seçilen AI provider'a gönderilebilir.
|
|
||||||
|
|
||||||
Bu yüzden provider seçimi bilinçli yapılmalıdır.
|
|
||||||
|
|
||||||
Daha hassas kullanım için:
|
|
||||||
|
|
||||||
- Sadece gerekli alanları context'e ekleyin.
|
|
||||||
- Finans ve müşteri detaylarını sınırlayın.
|
|
||||||
- Local model/Ollama seçeneklerini değerlendirin.
|
|
||||||
- Prompt içinde kesin hukuki, finansal veya klinik hüküm verilmemesini belirtin.
|
|
||||||
|
|
||||||
## Storage Güvenliği
|
|
||||||
|
|
||||||
`avatars` bucket public olabilir. Ama `project-assets` private kalmalıdır.
|
|
||||||
|
|
||||||
Proje görselleri müşteri işleriyle ilgili olabileceği için herkese açık URL ile servis edilmemelidir. Signed URL yaklaşımı daha güvenlidir.
|
|
||||||
|
|
||||||
Storage policy'lerinde:
|
|
||||||
|
|
||||||
- Kullanıcı kendi klasörüne erişebilmeli.
|
|
||||||
- Service role server-side işlemler için erişebilmeli.
|
|
||||||
- Başka kullanıcıların klasörlerine erişim engellenmeli.
|
|
||||||
|
|
||||||
## Auth Ayarları
|
|
||||||
|
|
||||||
Supabase Auth tarafında production domain doğru tanımlanmalıdır.
|
|
||||||
|
|
||||||
Kontrol edilecek yer:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
Authentication -> URL Configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
Özellikle:
|
|
||||||
|
|
||||||
- Site URL doğru mu?
|
|
||||||
- Redirect URL listesi production domaini içeriyor mu?
|
|
||||||
- Geliştirme ve production domainleri karışmış mı?
|
|
||||||
|
|
||||||
## İlk Admin Modeli
|
|
||||||
|
|
||||||
Neta public çok kullanıcılı kayıt modeliyle çalışmaz. İlk admin oluşturulduktan sonra kayıt kapanır.
|
|
||||||
|
|
||||||
Bu davranış korunmalıdır. Eğer public register sürekli açık kalırsa başka kullanıcılar sisteme girebilir ve beklenmeyen veri/rol problemleri oluşabilir.
|
|
||||||
|
|
||||||
## Production Checklist
|
|
||||||
|
|
||||||
Production'a çıkmadan önce şunları kontrol ederim:
|
|
||||||
|
|
||||||
- `.env.local` commit edilmedi.
|
|
||||||
- `SUPABASE_SERVICE_ROLE_KEY` sadece server env içinde.
|
|
||||||
- RLS açık.
|
|
||||||
- Client portal policy'leri test edildi.
|
|
||||||
- Storage bucket policy'leri test edildi.
|
|
||||||
- Supabase Auth URL ayarları doğru.
|
|
||||||
- İlk admin oluşturuldu.
|
|
||||||
- Public register kilitleniyor.
|
|
||||||
- Müşteri sadece kendi portal verisini görüyor.
|
|
||||||
- AI provider bilinçli seçildi.
|
|
||||||
|
|
||||||
Bu kontroller tamamlandığında Neta kişisel kullanım için daha güvenli ve öngörülebilir hâle gelir.
|
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
---
|
|
||||||
title: Performans ve UX Planı
|
|
||||||
description: Neta içindeki yavaş navigasyon, aksiyon geri bildirimi ve sayfa bazlı performans iyileştirmeleri için faz bazlı çalışma planı.
|
|
||||||
order: 17
|
|
||||||
status: legacy-v2-archive
|
|
||||||
---
|
|
||||||
|
|
||||||
# Performans ve UX Planı
|
|
||||||
|
|
||||||
> Arşiv: Bu v2 UX planı aktif çalışma planı değildir. Backend-first sıra ve kullanıcı yönlendirmeli Faz 10 tasarım süreci ana dönüşüm planında tanımlıdır.
|
|
||||||
|
|
||||||
Bu planı, uygulamada hissedilen iki ana problemi çözmek için hazırladım:
|
|
||||||
|
|
||||||
1. Butona ya da linke tıklayınca kullanıcıya hemen geri bildirim gelmemesi.
|
|
||||||
2. Sayfa geçişlerinde ve bazı veri işlemlerinde uygulamanın tıklamayı almamış gibi hissettirmesi.
|
|
||||||
|
|
||||||
Mevcut kodda problem tek bir noktadan çıkmıyor. Next.js App Router, server component veri çekme sürecini tamamlamadan yeni sayfayı göstermiyor. Bu normal, fakat kullanıcıya loading state verilmediğinde bekleme süresi bozukluk gibi algılanıyor. Aynı şekilde bazı server action formları veritabanını güncelliyor ama UI tarafında optimistic state olmadığı için değişiklik geç görünüyor.
|
|
||||||
|
|
||||||
## Sayfa Bazlı İnceleme
|
|
||||||
|
|
||||||
### Dashboard
|
|
||||||
|
|
||||||
Dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/page.tsx`
|
|
||||||
- `app/(dashboard)/dashboard-client.tsx`
|
|
||||||
|
|
||||||
Bulgular:
|
|
||||||
|
|
||||||
- Dashboard sayfasında sorgular sıralı çalışıyor. Görevler, projeler, finans, günlük kayıtları ve müşteriler peş peşe çekiliyor.
|
|
||||||
- Bazı sorgularda `select("*")` kullanılıyor. Dashboard için gereken kolonlar az olduğu halde tüm satır alanları taşınıyor.
|
|
||||||
- Hızlı ekle butonu ve sheet kaldırıldı. Bu alan artık sadece tarih filtresi taşıyor.
|
|
||||||
- Tamamlanan görev hesabında bazı yerlerde `completed`, bazı yerlerde `done` kullanımı var. Görev modülünün asıl statüsü `done`; bu tutarsızlık metrikleri yanlış gösterebilir.
|
|
||||||
|
|
||||||
İyileştirme yönü:
|
|
||||||
|
|
||||||
- Dashboard veri çekimi `Promise.all` ile paralel hale getirilmeli.
|
|
||||||
- `select("*")` yerine sadece gerekli kolonlar seçilmeli.
|
|
||||||
- Görev statüsü hesabı `done` standardına çekilmeli.
|
|
||||||
- Dashboard için lightweight loading skeleton eklenmeli.
|
|
||||||
|
|
||||||
### Görevler
|
|
||||||
|
|
||||||
Dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/tasks/page.tsx`
|
|
||||||
- `app/(dashboard)/tasks/tasks-client.tsx`
|
|
||||||
- `app/(dashboard)/tasks/actions.ts`
|
|
||||||
|
|
||||||
Bulgular:
|
|
||||||
|
|
||||||
- Görev ekleme dialog’u mobilde scroll problemi yaşıyordu. Dialog içeriği viewport yüksekliğine göre sınırlandı, gövde scroll edilebilir hale getirildi ve submit butonu footer içinde erişilebilir tutuldu.
|
|
||||||
- Kanban sürükle-bırak akışında optimistic update var.
|
|
||||||
- Liste görünümünde `Tamamla` ve `Sil` butonları doğrudan server action form’u kullanıyor. Bu yüzden veritabanı güncellense bile UI, route refresh/revalidation tamamlanana kadar değişmiyor.
|
|
||||||
- `TaskList` şu anda `handleTaskStatusChange` fonksiyonunu almıyor. Bu yüzden liste görünümü ile kanban görünümü aynı davranışı paylaşmıyor.
|
|
||||||
- Form submitlerinde pending state her yerde aynı değil. Bazı dialoglarda `isSubmitting` var, satır aksiyonlarında yok.
|
|
||||||
|
|
||||||
İyileştirme yönü:
|
|
||||||
|
|
||||||
- `TaskList` ve `TaskRow` içine optimistic status handler geçirilmeli.
|
|
||||||
- `Tamamla` aksiyonu liste ve kanban görünümünde aynı client-side optimistic akışı kullanmalı.
|
|
||||||
- Satır bazlı butonlarda pending spinner/disabled state olmalı.
|
|
||||||
- Hata durumunda task eski haline dönmeli ve kullanıcıya toast gösterilmeli.
|
|
||||||
- Silme aksiyonunda da optimistic remove + rollback uygulanmalı.
|
|
||||||
|
|
||||||
### Projeler
|
|
||||||
|
|
||||||
Dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/projects/page.tsx`
|
|
||||||
- `app/(dashboard)/projects/projects-client.tsx`
|
|
||||||
- `app/(dashboard)/projects/actions.ts`
|
|
||||||
|
|
||||||
Bulgular:
|
|
||||||
|
|
||||||
- Proje kartları `router.push` ile detay sayfasına gidiyor. Tıklama anında pending state olmadığı için 2-3 saniyelik veri çekme süresi kullanıcıya bozuk hissettiriyor.
|
|
||||||
- Liste görünümündeki detay butonu `Link` kullanıyor, fakat loading feedback yine yok.
|
|
||||||
- Proje kapak görselleri için her sayfa açılışında storage üzerinden signed URL üretiliyor. Proje sayısı arttıkça bu işlem liste açılışını yavaşlatabilir.
|
|
||||||
- Task istatistiği için tüm görevlerin `project_id, status` kolonları çekiliyor. Veri büyüdükçe bu iş RPC ya da aggregate sorguya alınmalı.
|
|
||||||
- Proje tamamlama aksiyonu server action form’u olarak çalışıyor, fakat satır/kart üzerinde pending veya optimistic durum yok.
|
|
||||||
|
|
||||||
İyileştirme yönü:
|
|
||||||
|
|
||||||
- Proje kartı `router.push` yerine erişilebilir bir `Link` yapısına dönüştürülmeli veya `PendingLink` wrapper ile route pending feedback almalı.
|
|
||||||
- Kart tıklanınca kart üzerinde kısa süreli loading/disabled state gösterilmeli.
|
|
||||||
- `router.prefetch` veya `Link` prefetch davranışı kritik detay sayfaları için kullanılmalı.
|
|
||||||
- Signed URL üretimi limitlenmeli, cache süresi netleştirilmeli veya sadece görünür kartlar için yapılmalı.
|
|
||||||
- Proje tamamlama aksiyonu için optimistic status/progress güncellemesi eklenmeli.
|
|
||||||
|
|
||||||
### Proje Detayı
|
|
||||||
|
|
||||||
Dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/projects/[id]/page.tsx`
|
|
||||||
- `app/(dashboard)/projects/[id]/project-detail-client.tsx`
|
|
||||||
|
|
||||||
Bulgular:
|
|
||||||
|
|
||||||
- Detay sayfası aynı anda proje, planlama alanları, görevler, finans kayıtları ve revizyonları çekiyor. Bu doğru şekilde `Promise.all` ile yapılmış, fakat sayfa yine ağır olabilir.
|
|
||||||
- Kapak görseli için signed URL ayrıca üretiliyor.
|
|
||||||
- Görevler sekmesinde kanban sürükle-bırak optimistic çalışıyor.
|
|
||||||
- Aynı sekmedeki `Tamamla` butonu ise doğrudan server action form’u. Bu yüzden butona basınca local task state güncellenmiyor.
|
|
||||||
- Planlama alanı silme, proje tamamlama ve proje ayarları işlemlerinde standart pending/toast yok.
|
|
||||||
- Sekmeler client-side olduğu için hızlı, fakat ilk sayfa yükü bütün sekme verilerini baştan taşıyor.
|
|
||||||
|
|
||||||
İyileştirme yönü:
|
|
||||||
|
|
||||||
- Proje görevlerindeki `Tamamla` butonu `handleTaskStatusChange` akışını kullanmalı.
|
|
||||||
- Proje detayında task list ve task kanban aynı optimistic action modelini paylaşmalı.
|
|
||||||
- Ağır sekmeler için ileride route segment veya lazy data yaklaşımı değerlendirilmeli.
|
|
||||||
- Planlama alanı silme ve proje tamamlama için ortak pending button kullanılmalı.
|
|
||||||
|
|
||||||
### Müşteriler
|
|
||||||
|
|
||||||
Dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/clients/page.tsx`
|
|
||||||
- `app/(dashboard)/clients/clients-client.tsx`
|
|
||||||
- `app/(dashboard)/clients/[id]/page.tsx`
|
|
||||||
|
|
||||||
Bulgular:
|
|
||||||
|
|
||||||
- Pipeline sürükle-bırak optimistic update yapıyor. Bu iyi bir örnek.
|
|
||||||
- Müşteri kart ve satır linklerinde pending feedback yok.
|
|
||||||
- Müşteri detay sayfasında müşteri ve activity verisi sıralı çekiliyor. Activity sorgusu client id’ye bağlı olduğu için pratikte sorun küçük, fakat sayfa açılışında loading skeleton yok.
|
|
||||||
- Müşteri dialoglarında submit pending var, fakat hata toast’u standart değil.
|
|
||||||
|
|
||||||
İyileştirme yönü:
|
|
||||||
|
|
||||||
- Müşteri linkleri `PendingLink` ile tıklama feedback’i almalı.
|
|
||||||
- Detail sayfasına skeleton eklenmeli.
|
|
||||||
- Dialog submit hata/success davranışı proje dialog’larıyla aynı standarda çekilmeli.
|
|
||||||
|
|
||||||
### Finans
|
|
||||||
|
|
||||||
Dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/finance/page.tsx`
|
|
||||||
- `app/(dashboard)/finance/finance-client.tsx`
|
|
||||||
- `app/(dashboard)/finance/actions.ts`
|
|
||||||
|
|
||||||
Bulgular:
|
|
||||||
|
|
||||||
- Sayfa veri çekimi paralel yapılıyor.
|
|
||||||
- Transaction silme server action form’u ile çalışıyor, fakat satır bazlı pending feedback yok.
|
|
||||||
- Dialog scroll/pending deseni görev sayfasındaki yeni mobil pattern ile aynı standarda çekilmeli.
|
|
||||||
- Veri büyüdüğünde tüm finans kayıtlarını çekmek yerine dönem filtresi veya pagination gerekebilir.
|
|
||||||
|
|
||||||
İyileştirme yönü:
|
|
||||||
|
|
||||||
- Silme ve form submitlerinde ortak pending button kullanılmalı.
|
|
||||||
- Listeye dönem filtresi veya pagination eklenmeli.
|
|
||||||
- Mobil dialog düzeni standart hale getirilmeli.
|
|
||||||
|
|
||||||
### Takvim
|
|
||||||
|
|
||||||
Dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/calendar/page.tsx`
|
|
||||||
- `app/(dashboard)/calendar/calendar-client.tsx`
|
|
||||||
- `app/(dashboard)/calendar/actions.ts`
|
|
||||||
|
|
||||||
Bulgular:
|
|
||||||
|
|
||||||
- Sayfa veri çekimi paralel yapılıyor.
|
|
||||||
- Etkinlik silme server action form’u ile çalışıyor, pending feedback yok.
|
|
||||||
- Tüm etkinlikler çekiliyor. Veri büyüdüğünde ay bazlı sorgu daha doğru olur.
|
|
||||||
|
|
||||||
İyileştirme yönü:
|
|
||||||
|
|
||||||
- Ay görünümüne göre tarih aralıklı sorgu planlanmalı.
|
|
||||||
- Silme ve kayıt işlemleri ortak pending/toast modeline alınmalı.
|
|
||||||
- Takvim sayfası için skeleton eklenmeli.
|
|
||||||
|
|
||||||
### Analizler
|
|
||||||
|
|
||||||
Dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/analytics/page.tsx`
|
|
||||||
- `app/(dashboard)/analytics/analytics-client.tsx`
|
|
||||||
|
|
||||||
Bulgular:
|
|
||||||
|
|
||||||
- Görev, proje ve finans sorguları sıralı çalışıyor.
|
|
||||||
- Üç tabloda da `select("*")` kullanılıyor.
|
|
||||||
- Analiz ekranı genellikle aggregate veri ister; tüm satırları taşımak gereksiz büyüyebilir.
|
|
||||||
|
|
||||||
İyileştirme yönü:
|
|
||||||
|
|
||||||
- Sorgular `Promise.all` ile paralelleştirilmeli.
|
|
||||||
- Gerekli kolonlara düşürülmeli.
|
|
||||||
- Orta vadede Supabase RPC veya view ile aggregate veri dönülmeli.
|
|
||||||
- Sayfa için skeleton eklenmeli.
|
|
||||||
|
|
||||||
### Layout ve Navigasyon
|
|
||||||
|
|
||||||
Dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/layout.tsx`
|
|
||||||
- `components/layout/dashboard-shell.tsx`
|
|
||||||
- `config/sidebar.ts`
|
|
||||||
|
|
||||||
Bulgular:
|
|
||||||
|
|
||||||
- Dashboard layout her route geçişinde auth user ve profil bilgisini kontrol ediyor.
|
|
||||||
- Sidebar linklerinde route geçiş pending feedback’i yok.
|
|
||||||
- Mobil sidebar linke tıklayınca kapanıyor ama route yüklenene kadar ana içerik eski kalıyor.
|
|
||||||
- App Router yeni route’un server component payload’unu beklerken kullanıcıya global bir progress göstergesi verilmediği için tıklama çalışmamış gibi hissediliyor.
|
|
||||||
|
|
||||||
İyileştirme yönü:
|
|
||||||
|
|
||||||
- Global route pending feedback eklenmeli.
|
|
||||||
- Sidebar linkleri aktif/pending durumunu gösterecek wrapper ile değiştirilmeli.
|
|
||||||
- `app/(dashboard)/loading.tsx` ve kritik route loading dosyaları eklenmeli.
|
|
||||||
- Layout profil sorgusu mümkünse cache/helper seviyesinde sadeleştirilmeli.
|
|
||||||
|
|
||||||
## Faz Bazlı Plan
|
|
||||||
|
|
||||||
### Faz 1: Acil UX Düzeltmeleri
|
|
||||||
|
|
||||||
Amaç: Butona basınca anında tepki almak.
|
|
||||||
|
|
||||||
Yapılacaklar:
|
|
||||||
|
|
||||||
- `PendingSubmitButton` bileşeni oluşturulacak.
|
|
||||||
- Server action form’larında `useFormStatus` ile buton loading/disabled hale getirilecek.
|
|
||||||
- Görev listesi `Tamamla` butonu optimistic update kullanacak.
|
|
||||||
- Proje detayındaki görev `Tamamla` butonları aynı optimistic update modeline alınacak.
|
|
||||||
- Silme işlemlerinde satır bazlı loading ve mümkün olan yerlerde optimistic remove eklenecek.
|
|
||||||
- Hata durumunda rollback + toast standardı uygulanacak.
|
|
||||||
|
|
||||||
Öncelikli dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/tasks/tasks-client.tsx`
|
|
||||||
- `app/(dashboard)/projects/[id]/project-detail-client.tsx`
|
|
||||||
- `app/(dashboard)/tasks/actions.ts`
|
|
||||||
|
|
||||||
Kabul kriteri:
|
|
||||||
|
|
||||||
- Görev listesinde `Tamamla` tıklandığında satır hemen tamamlanmış görünmeli.
|
|
||||||
- Buton işlem süresince disabled/loading olmalı.
|
|
||||||
- Hata olursa görev eski statüsüne dönmeli.
|
|
||||||
|
|
||||||
### Faz 2: Navigasyon Feedback’i
|
|
||||||
|
|
||||||
Amaç: Linke tıklandığında kullanıcı anında geçişin başladığını görmeli.
|
|
||||||
|
|
||||||
Yapılacaklar:
|
|
||||||
|
|
||||||
- `PendingLink` veya `NavigationProgressProvider` oluşturulacak.
|
|
||||||
- Pathname değişene kadar linkte pending state tutulacak.
|
|
||||||
- Sidebar linkleri, dashboard recent item linkleri, müşteri/proje liste linkleri bu wrapper ile güncellenecek.
|
|
||||||
- Proje kartlarındaki `router.push` kullanımı ya `Link` tabanlı karta çevrilecek ya da explicit pending state ile desteklenecek.
|
|
||||||
- Kritik kart/detail linklerinde hover/focus sırasında prefetch yapılacak.
|
|
||||||
|
|
||||||
Öncelikli dosyalar:
|
|
||||||
|
|
||||||
- `components/layout/dashboard-shell.tsx`
|
|
||||||
- `app/(dashboard)/projects/projects-client.tsx`
|
|
||||||
- `app/(dashboard)/clients/clients-client.tsx`
|
|
||||||
- `app/(dashboard)/dashboard-client.tsx`
|
|
||||||
|
|
||||||
Kabul kriteri:
|
|
||||||
|
|
||||||
- Sidebar veya proje kartına tıklandığında anında visual feedback görünmeli.
|
|
||||||
- Kullanıcı eski sayfada beklerken tıklama boşa gitmiş gibi hissetmemeli.
|
|
||||||
|
|
||||||
### Faz 3: Route Loading Skeletonları
|
|
||||||
|
|
||||||
Amaç: Server component verisi beklenirken boş veya donmuş ekran hissini kaldırmak.
|
|
||||||
|
|
||||||
Yapılacaklar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/loading.tsx` eklenecek.
|
|
||||||
- Kritik sayfalar için özel loading skeletonları hazırlanacak:
|
|
||||||
- `app/(dashboard)/projects/loading.tsx`
|
|
||||||
- `app/(dashboard)/projects/[id]/loading.tsx`
|
|
||||||
- `app/(dashboard)/tasks/loading.tsx`
|
|
||||||
- `app/(dashboard)/clients/loading.tsx`
|
|
||||||
- `app/(dashboard)/analytics/loading.tsx`
|
|
||||||
- Skeletonlar gerçek sayfa layout’una benzeyecek, sadece placeholder gösterecek.
|
|
||||||
|
|
||||||
Kabul kriteri:
|
|
||||||
|
|
||||||
- Sayfa geçişinde eski sayfada donuk bekleme yerine kontrollü loading görünmeli.
|
|
||||||
- Mobilde skeleton içerik taşmamalı.
|
|
||||||
|
|
||||||
### Faz 4: Veri Çekme Optimizasyonu
|
|
||||||
|
|
||||||
Amaç: Sayfa açılışlarını gerçekten hızlandırmak.
|
|
||||||
|
|
||||||
Yapılacaklar:
|
|
||||||
|
|
||||||
- Dashboard sorguları paralel hale getirilecek.
|
|
||||||
- Analytics sorguları paralel hale getirilecek.
|
|
||||||
- Dashboard ve analytics tarafındaki `select("*")` kullanımları kaldırılacak.
|
|
||||||
- Projeler sayfasında task istatistiği aggregate sorgu/RPC ile alınacak.
|
|
||||||
- Finans, takvim ve analytics için pagination veya tarih aralığı filtresi planlanacak.
|
|
||||||
- Proje kapak görsellerinde signed URL üretimi gözden geçirilecek; gerekirse sadece ihtiyaç duyulan görseller için URL üretilecek.
|
|
||||||
|
|
||||||
Öncelikli dosyalar:
|
|
||||||
|
|
||||||
- `app/(dashboard)/page.tsx`
|
|
||||||
- `app/(dashboard)/analytics/page.tsx`
|
|
||||||
- `app/(dashboard)/projects/page.tsx`
|
|
||||||
- `supabase/setup.sql`
|
|
||||||
|
|
||||||
Kabul kriteri:
|
|
||||||
|
|
||||||
- Dashboard ve analytics route response süreleri ölçülebilir şekilde düşmeli.
|
|
||||||
- Gereksiz kolon taşınmamalı.
|
|
||||||
- Proje listesi fazla görselli projelerde daha stabil açılmalı.
|
|
||||||
|
|
||||||
### Faz 5: Mutation Standardı
|
|
||||||
|
|
||||||
Amaç: Tüm ekleme, güncelleme, silme ve tamamlama işlemleri aynı hissi vermeli.
|
|
||||||
|
|
||||||
Yapılacaklar:
|
|
||||||
|
|
||||||
- Ortak action button kalıbı oluşturulacak.
|
|
||||||
- Dialog submitlerinde success/error toast standardı uygulanacak.
|
|
||||||
- Server action dönüşleri mümkünse `{ ok, message }` yapısına yaklaştırılacak.
|
|
||||||
- `revalidatePath` kapsamı her action için tekrar kontrol edilecek.
|
|
||||||
- `done` ve `completed` statü kullanımı standardize edilecek.
|
|
||||||
|
|
||||||
Kabul kriteri:
|
|
||||||
|
|
||||||
- Görev, proje, müşteri, finans, takvim ve günlük formları aynı pending/hata/success davranışını göstermeli.
|
|
||||||
- Statü değişimleri UI’da tutarlı görünmeli.
|
|
||||||
|
|
||||||
### Faz 6: Mobil ve Touch Deneyimi
|
|
||||||
|
|
||||||
Amaç: Mobilde form, dialog ve kanban akışlarını güvenli hale getirmek.
|
|
||||||
|
|
||||||
Yapılacaklar:
|
|
||||||
|
|
||||||
- Görev dialog’unda uygulanan scroll pattern diğer uzun dialoglara taşınacak.
|
|
||||||
- Proje ekleme, finans ekleme, takvim etkinliği ve günlük dialogları mobilde test edilecek.
|
|
||||||
- Drag/drop kullanılan alanlarda mobil için butonlu alternatif durum değişimi eklenecek.
|
|
||||||
- Buton hit area ve sticky footer davranışları kontrol edilecek.
|
|
||||||
|
|
||||||
Kabul kriteri:
|
|
||||||
|
|
||||||
- Mobilde hiçbir uzun form submit butonunu viewport dışında bırakmamalı.
|
|
||||||
- Drag/drop olmadan da statü değiştirilebilmeli.
|
|
||||||
|
|
||||||
### Faz 7: Test ve Ölçüm
|
|
||||||
|
|
||||||
Amaç: İyileştirmelerin gerçekten çalıştığını doğrulamak.
|
|
||||||
|
|
||||||
Yapılacaklar:
|
|
||||||
|
|
||||||
- `npm run build` çalıştırılacak.
|
|
||||||
- Dashboard, görevler, projeler, proje detayı, müşteriler, finans ve takvim için manuel smoke test yapılacak.
|
|
||||||
- Mobil viewport’ta görev/proje/finans dialogları test edilecek.
|
|
||||||
- Route geçişleri için basit ölçüm notu tutulacak.
|
|
||||||
- Kritik aksiyonlarda hata senaryosu test edilecek.
|
|
||||||
|
|
||||||
Kabul kriteri:
|
|
||||||
|
|
||||||
- Build başarılı olmalı.
|
|
||||||
- Görev tamamla, proje detayına gitme ve sidebar navigasyonu görsel feedback vermeli.
|
|
||||||
- Mobilde görev formu scroll ve submit açısından çalışmalı.
|
|
||||||
|
|
||||||
## Uygulama Sırası
|
|
||||||
|
|
||||||
Önce Faz 1 uygulanmalı. Çünkü kullanıcının “Göreve tamamla dediğimde aksiyon almıyor gibi” hissi doğrudan buradan geliyor.
|
|
||||||
|
|
||||||
Ardından Faz 2 ve Faz 3 beraber ilerlemeli. Navigasyon pending state’i ve route skeletonları aynı problemi iki farklı anda çözüyor: tıklama anı ve route veri bekleme anı.
|
|
||||||
|
|
||||||
Faz 4 daha teknik performans tarafı. İlk üç faz UX hissini hızlıca düzeltir, Faz 4 gerçek veri süresini azaltır.
|
|
||||||
|
|
||||||
Faz 5 ve Faz 6 uygulamanın tamamına kalite standardı yayar. Faz 7 her fazdan sonra küçük parçalar halinde de çalıştırılabilir, fakat büyük UX değişimlerinden sonra mutlaka yapılmalı.
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# 0001 Initial Schema
|
|
||||||
|
|
||||||
SQL file: `supabase/schema.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
This is the first registered database query file for the project. It represents the schema that existed before the Freelancer OS MVP planning work started.
|
|
||||||
|
|
||||||
## What It Creates
|
|
||||||
|
|
||||||
- `journals`
|
|
||||||
- `tasks`
|
|
||||||
- `chat_sessions`
|
|
||||||
- `chat_messages`
|
|
||||||
- `profiles`
|
|
||||||
- Row Level Security policies for the tables above
|
|
||||||
- `handle_new_user()` trigger function for profile creation
|
|
||||||
- `on_auth_user_created` trigger
|
|
||||||
- `avatars` storage bucket and related storage policies
|
|
||||||
|
|
||||||
## Current Product Fit
|
|
||||||
|
|
||||||
This schema supports the earlier MindSpace/Cognis prototype:
|
|
||||||
|
|
||||||
- mood/energy journals
|
|
||||||
- basic tasks
|
|
||||||
- chat history
|
|
||||||
- user profile storage
|
|
||||||
- avatar uploads
|
|
||||||
|
|
||||||
It does not yet fully match the Freelancer OS MVP model.
|
|
||||||
|
|
||||||
## Known Gaps For MVP
|
|
||||||
|
|
||||||
The Freelancer OS MVP still needs new schema additions for:
|
|
||||||
|
|
||||||
- `clients`
|
|
||||||
- `projects`
|
|
||||||
- `calendar_events`
|
|
||||||
- `finance_transactions`
|
|
||||||
- `daily_logs`
|
|
||||||
- `app_settings`
|
|
||||||
|
|
||||||
The existing `journals` table can either be migrated into `daily_logs` or kept as a legacy table until the UI is moved to the new model.
|
|
||||||
|
|
||||||
## Execution Notes
|
|
||||||
|
|
||||||
This file should be treated as the first baseline.
|
|
||||||
|
|
||||||
It was updated to be safer for Supabase SQL Editor retries:
|
|
||||||
|
|
||||||
- tables use `create table if not exists`
|
|
||||||
- policies are dropped before being recreated
|
|
||||||
- the profile trigger is dropped before being recreated
|
|
||||||
- the PL/pgSQL function uses the correct `$$` delimiter
|
|
||||||
- profile creation uses `on conflict (id) do nothing`
|
|
||||||
|
|
||||||
If a previous run failed halfway through, rerunning this baseline should be safe for the current schema shape.
|
|
||||||
|
|
||||||
Future changes should be added as ordered migration files rather than editing this baseline after execution.
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# 0002 Freelancer OS Core Tables
|
|
||||||
|
|
||||||
SQL file: `supabase/migrations/0002_add_freelancer_os_core_tables.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Adds the database model required for the Freelancer OS MVP.
|
|
||||||
|
|
||||||
This migration keeps the existing baseline from `supabase/schema.sql` and extends it instead of replacing it. The existing `tasks` table is reused and enriched with freelancer-specific fields.
|
|
||||||
|
|
||||||
## What It Creates
|
|
||||||
|
|
||||||
- `clients`
|
|
||||||
- `projects`
|
|
||||||
- `calendar_events`
|
|
||||||
- `finance_transactions`
|
|
||||||
- `daily_logs`
|
|
||||||
- `app_settings`
|
|
||||||
- `set_updated_at()` trigger function
|
|
||||||
- updated-at triggers for the new tables
|
|
||||||
- indexes for common dashboard/report queries
|
|
||||||
- RLS policies for every new table
|
|
||||||
|
|
||||||
## What It Changes
|
|
||||||
|
|
||||||
The existing `tasks` table gets these additional columns:
|
|
||||||
|
|
||||||
- `client_id`
|
|
||||||
- `project_id`
|
|
||||||
- `priority`
|
|
||||||
- `due_at`
|
|
||||||
- `estimated_minutes`
|
|
||||||
- `actual_minutes`
|
|
||||||
|
|
||||||
This lets a task belong to a client and/or project while preserving the earlier journal/task prototype schema.
|
|
||||||
|
|
||||||
## MVP Coverage
|
|
||||||
|
|
||||||
This migration supports:
|
|
||||||
|
|
||||||
- client management
|
|
||||||
- client projects
|
|
||||||
- side projects
|
|
||||||
- task planning
|
|
||||||
- calendar planning
|
|
||||||
- income and expense tracking
|
|
||||||
- daily mood/energy logging
|
|
||||||
- app and AI provider settings
|
|
||||||
|
|
||||||
## RLS Model
|
|
||||||
|
|
||||||
Every new table has `user_id`.
|
|
||||||
|
|
||||||
Policies follow the same pattern:
|
|
||||||
|
|
||||||
- users can select their own rows
|
|
||||||
- users can insert rows only for themselves
|
|
||||||
- users can update their own rows
|
|
||||||
- users can delete their own rows
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
`app_settings.api_key` exists for compatibility with the current prototype flow. For production-grade use, provider credentials should be encrypted or moved to a safer secret-management strategy.
|
|
||||||
|
|
||||||
`daily_logs` is the new MVP-oriented replacement for the earlier `journals` concept. The old `journals` table remains available until the UI migration is complete.
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
Run this after `supabase/schema.sql`.
|
|
||||||
|
|
||||||
Do not add this file to `query-log.md` until it has actually been executed against a database.
|
|
||||||
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
# 0003 - Project Planning Assets
|
|
||||||
|
|
||||||
SQL file: `supabase/migrations/0003_add_project_planning_assets.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
This migration extends project management beyond task tracking.
|
|
||||||
|
|
||||||
It adds:
|
|
||||||
|
|
||||||
- Project cover image fields on `projects`
|
|
||||||
- A private Supabase Storage bucket for uploaded project images
|
|
||||||
- A structured `project_planning_sections` table for project planning categories
|
|
||||||
|
|
||||||
## Project Image Fields
|
|
||||||
|
|
||||||
The `projects` table receives:
|
|
||||||
|
|
||||||
- `cover_image_path`: path of the uploaded file in the `project-assets` bucket
|
|
||||||
- `cover_image_alt`: optional alt text for the cover image
|
|
||||||
|
|
||||||
Images are uploaded from the user's computer, not saved as external image links.
|
|
||||||
|
|
||||||
## Storage Bucket
|
|
||||||
|
|
||||||
Bucket:
|
|
||||||
|
|
||||||
- `project-assets`
|
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
- Private bucket
|
|
||||||
- Max file size: 5 MB
|
|
||||||
- Allowed MIME types:
|
|
||||||
- `image/jpeg`
|
|
||||||
- `image/png`
|
|
||||||
- `image/webp`
|
|
||||||
- `image/gif`
|
|
||||||
|
|
||||||
Storage object paths must start with the authenticated user id:
|
|
||||||
|
|
||||||
```txt
|
|
||||||
<user_id>/projects/<project_id>/<file_name>
|
|
||||||
```
|
|
||||||
|
|
||||||
This keeps Storage RLS simple and user-scoped.
|
|
||||||
|
|
||||||
## Planning Sections
|
|
||||||
|
|
||||||
Table:
|
|
||||||
|
|
||||||
- `project_planning_sections`
|
|
||||||
|
|
||||||
Core fields:
|
|
||||||
|
|
||||||
- `user_id`
|
|
||||||
- `project_id`
|
|
||||||
- `category`
|
|
||||||
- `title`
|
|
||||||
- `content`
|
|
||||||
- `metadata`
|
|
||||||
- `sort_order`
|
|
||||||
|
|
||||||
Allowed categories:
|
|
||||||
|
|
||||||
- `overview`
|
|
||||||
- `problem`
|
|
||||||
- `goal`
|
|
||||||
- `audience`
|
|
||||||
- `scope`
|
|
||||||
- `design_system`
|
|
||||||
- `color_palette`
|
|
||||||
- `typography`
|
|
||||||
- `assets`
|
|
||||||
- `notes`
|
|
||||||
|
|
||||||
## RLS
|
|
||||||
|
|
||||||
RLS is enabled for `project_planning_sections`.
|
|
||||||
|
|
||||||
Users can only select, insert, update, and delete their own planning sections.
|
|
||||||
|
|
||||||
Storage policies allow users to select, upload, update, and delete only files under their own user id folder inside `project-assets`.
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
Run after:
|
|
||||||
|
|
||||||
1. `supabase/schema.sql`
|
|
||||||
2. `supabase/migrations/0002_add_freelancer_os_core_tables.sql`
|
|
||||||
|
|
||||||
After running this SQL in Supabase, add an execution record to `docs/database/query-log.md`.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# 0004 - Business OS Tables
|
|
||||||
|
|
||||||
SQL file: `supabase/migrations/0004_add_business_os_tables.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Adds business document and recurring cost tables used by the freelancer business layer.
|
|
||||||
|
|
||||||
## What It Creates
|
|
||||||
|
|
||||||
- `proposals`
|
|
||||||
- `contracts`
|
|
||||||
- `invoices`
|
|
||||||
- `subscriptions`
|
|
||||||
|
|
||||||
## RLS
|
|
||||||
|
|
||||||
Every table has `user_id` and row level security policies that allow users to select, insert, update, and delete only their own rows.
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
Run after `0003_add_project_planning_assets.sql`.
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# 0005 - Advanced CRM Tables
|
|
||||||
|
|
||||||
SQL file: `supabase/migrations/0005_add_advanced_crm_tables.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Extends client management with pipeline and activity tracking fields.
|
|
||||||
|
|
||||||
## What It Changes
|
|
||||||
|
|
||||||
The `clients` table receives:
|
|
||||||
|
|
||||||
- `pipeline_stage`
|
|
||||||
- `next_follow_up_date`
|
|
||||||
- `last_contact_date`
|
|
||||||
- `client_value_score`
|
|
||||||
|
|
||||||
## What It Creates
|
|
||||||
|
|
||||||
- `client_activities`
|
|
||||||
|
|
||||||
## RLS
|
|
||||||
|
|
||||||
`client_activities` is user-scoped through `user_id` and has select, insert, update, and delete policies for the owning user.
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
Run after `0004_add_business_os_tables.sql`.
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# 0006 - pgvector And Embeddings
|
|
||||||
|
|
||||||
SQL file: `supabase/migrations/0006_add_pgvector_and_embeddings.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Adds vector storage for future retrieval augmented generation features.
|
|
||||||
|
|
||||||
## What It Creates
|
|
||||||
|
|
||||||
- `vector` extension
|
|
||||||
- `document_embeddings`
|
|
||||||
- `match_documents()`
|
|
||||||
|
|
||||||
## RLS
|
|
||||||
|
|
||||||
Embeddings are scoped by `user_id`; users can manage only their own embedding rows.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
The migration expects the target Postgres/Supabase environment to support the `vector` extension.
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
Run after `0005_add_advanced_crm_tables.sql`.
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# 0007 - Client Portal Tables
|
|
||||||
|
|
||||||
SQL file: `supabase/migrations/0007_add_client_portal_tables.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Adds client portal access and revision request support.
|
|
||||||
|
|
||||||
## What It Changes
|
|
||||||
|
|
||||||
- Adds `profiles.role`
|
|
||||||
- Adds `clients.client_auth_id`
|
|
||||||
- Adds `tasks.is_public_to_client`
|
|
||||||
|
|
||||||
## What It Creates
|
|
||||||
|
|
||||||
- `project_revisions`
|
|
||||||
|
|
||||||
## RLS
|
|
||||||
|
|
||||||
Freelancers manage revisions for their own projects. Clients can view their linked client record, related projects, public project tasks, project planning sections, and their own revision requests.
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
Run after `0006_add_pgvector_and_embeddings.sql`.
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# 0008 - Project Progress And Quota
|
|
||||||
|
|
||||||
SQL file: `supabase/migrations/0008_add_project_progress_and_quota.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Adds project progress mode and revision quota support.
|
|
||||||
|
|
||||||
## What It Changes
|
|
||||||
|
|
||||||
The `projects` table receives:
|
|
||||||
|
|
||||||
- `progress_type`
|
|
||||||
- `revision_quota`
|
|
||||||
|
|
||||||
## What It Creates
|
|
||||||
|
|
||||||
- `update_project_progress_on_task_change()`
|
|
||||||
- `trigger_update_project_progress`
|
|
||||||
- `update_project_progress_on_type_change()`
|
|
||||||
- `trigger_update_project_progress_type`
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
Automatic progress mode calculates progress from tasks with status `done`.
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
Run after `0007_add_client_portal_tables.sql`.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# 0009 - Lock Registration After First Admin
|
|
||||||
|
|
||||||
SQL file:
|
|
||||||
|
|
||||||
`supabase/migrations/0009_lock_registration_after_first_admin.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Adds the first-time setup guard for self-hosted installations.
|
|
||||||
|
|
||||||
The `/register` page is only available while the system has no profile record. After the first account creates a profile, public registration is closed.
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
- Adds `public.is_first_admin_setup_available()`.
|
|
||||||
- Grants the function to `anon` and `authenticated` roles so the app can check setup state safely without bypassing RLS manually.
|
|
||||||
- Replaces `public.handle_new_user()` so direct public Supabase Auth signup attempts are also rejected after the first profile exists.
|
|
||||||
- Allows service-role/admin-created users when `raw_app_meta_data.internal_created` is `true`, so future invite/client-portal flows can still create accounts intentionally.
|
|
||||||
|
|
||||||
## Behavior
|
|
||||||
|
|
||||||
1. Fresh install has no `public.profiles` rows.
|
|
||||||
2. `/register` stays open.
|
|
||||||
3. The first signup creates an auth user and the trigger creates the first profile.
|
|
||||||
4. The setup function starts returning `false`.
|
|
||||||
5. `/register` redirects to `/login`.
|
|
||||||
6. Further public signup attempts fail at the database trigger level.
|
|
||||||
7. Admin-created internal users can still be allowed by service-role flows that set `app_metadata.internal_created = true`.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- This is intended for the MVP single-admin self-host model.
|
|
||||||
- If multi-user, client portal accounts, invites, or team members are re-enabled later, keep them behind service-role/admin-created flows instead of public signup.
|
|
||||||
- Do not add this SQL file to `query-log.md` until it has actually been run in the target Supabase environment.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# 0010 - Internal Auth User Creation
|
|
||||||
|
|
||||||
SQL file:
|
|
||||||
|
|
||||||
`supabase/migrations/0010_allow_internal_auth_user_creation.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Allows Neta's trusted server-side flows to create Auth users after public registration is locked.
|
|
||||||
|
|
||||||
This is required for client portal accounts. Public signup remains blocked after the first admin account, but the application can create internal users through the service role key.
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
- Adds `neta_internal.internal_auth_creations`.
|
|
||||||
- Adds `public.request_internal_auth_creation(target_email, target_reason)`.
|
|
||||||
- Grants that RPC only to `service_role`.
|
|
||||||
- Updates `public.handle_new_user()` so it accepts a short-lived internal creation request.
|
|
||||||
- Reloads the PostgREST schema cache.
|
|
||||||
|
|
||||||
## Operational Notes
|
|
||||||
|
|
||||||
The application calls the RPC immediately before calling the Supabase Auth Admin API. The pending request expires after two minutes and is consumed by the Auth user trigger.
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# 0011 - Service Role Claims And Storage Policies
|
|
||||||
|
|
||||||
SQL file:
|
|
||||||
|
|
||||||
`supabase/migrations/0011_fix_service_role_claims_and_storage_policies.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Fixes service-role detection for self-hosted PostgREST deployments and allows trusted server-side storage operations to pass RLS.
|
|
||||||
|
|
||||||
PostgREST stores JWT claims in `request.jwt.claims` JSON when legacy GUCs are disabled. Earlier SQL only checked the legacy `request.jwt.claim.role` value, so service-role requests could be rejected.
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
- Adds `public.neta_current_jwt_role()`.
|
|
||||||
- Updates `public.request_internal_auth_creation()` to read both modern and legacy JWT claim formats.
|
|
||||||
- Recreates avatar and project asset storage policies to allow service-role operations.
|
|
||||||
- Reloads the PostgREST schema cache.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# Legacy PostgreSQL database documentation
|
|
||||||
|
|
||||||
Bu klasördeki `0001`–`0011`, query log/order ve seed belgeleri Neta v2 Supabase/PostgreSQL geçmişinin arşividir. Güncel runtime veya migration talimatı değildir.
|
|
||||||
|
|
||||||
Self-hosted v3'te:
|
|
||||||
|
|
||||||
- schema kaynağı `server/db/schema`,
|
|
||||||
- migration kaynağı `server/db/migrations`,
|
|
||||||
- migration runner `pnpm db:migrate`,
|
|
||||||
- veri aktarım runbook'u `docs/self-hosted-redesign/phase-8-import-release.md`
|
|
||||||
|
|
||||||
olarak kullanılır.
|
|
||||||
|
|
||||||
Arşiv dosyaları yalnızca eski kolon/policy davranışlarını import sırasında karşılaştırmak için korunur. Yeni migration veya seed bu klasöre eklenmemelidir.
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Database Query Log
|
|
||||||
|
|
||||||
This file records SQL files that were executed against a database environment.
|
|
||||||
|
|
||||||
Do not mark a query as executed unless it was actually run.
|
|
||||||
|
|
||||||
| Date | Environment | Order | SQL file | Runner | Result | Notes |
|
|
||||||
| --- | --- | --- | --- | --- | --- | --- |
|
|
||||||
| Not recorded | Unknown existing environment | 0001 | `supabase/schema.sql` | Unknown | Assumed existing baseline | File existed before this query log was introduced. Confirm manually before rerunning. |
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Database Query Order
|
|
||||||
|
|
||||||
This file is the canonical order of SQL files for database setup and migration.
|
|
||||||
|
|
||||||
| Order | SQL file | Documentation | Status |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| 0001 | `supabase/schema.sql` | `docs/database/0001-initial-schema.md` | Baseline registered |
|
|
||||||
| 0002 | `supabase/migrations/0002_add_freelancer_os_core_tables.sql` | `docs/database/0002-freelancer-os-core-tables.md` | Pending execution |
|
|
||||||
| 0003 | `supabase/migrations/0003_add_project_planning_assets.sql` | `docs/database/0003-project-planning-assets.md` | Pending execution |
|
|
||||||
| 0004 | `supabase/migrations/0004_add_business_os_tables.sql` | `docs/database/0004-business-os-tables.md` | Pending execution |
|
|
||||||
| 0005 | `supabase/migrations/0005_add_advanced_crm_tables.sql` | `docs/database/0005-advanced-crm-tables.md` | Pending execution |
|
|
||||||
| 0006 | `supabase/migrations/0006_add_pgvector_and_embeddings.sql` | `docs/database/0006-pgvector-and-embeddings.md` | Pending execution |
|
|
||||||
| 0007 | `supabase/migrations/0007_add_client_portal_tables.sql` | `docs/database/0007-client-portal-tables.md` | Pending execution |
|
|
||||||
| 0008 | `supabase/migrations/0008_add_project_progress_and_quota.sql` | `docs/database/0008-project-progress-and-quota.md` | Pending execution |
|
|
||||||
| 0009 | `supabase/migrations/0009_lock_registration_after_first_admin.sql` | `docs/database/0009-lock-registration-after-first-admin.md` | Pending execution |
|
|
||||||
| 0010 | `supabase/migrations/0010_allow_internal_auth_user_creation.sql` | `docs/database/0010-internal-auth-user-creation.md` | Pending execution |
|
|
||||||
| 0011 | `supabase/migrations/0011_fix_service_role_claims_and_storage_policies.sql` | `docs/database/0011-service-role-claims-and-storage-policies.md` | Pending execution |
|
|
||||||
| seed-0001 | `supabase/seeds/0001_demo_freelancer_os_data.sql` | `docs/database/seed-0001-demo-freelancer-os-data.md` | Optional demo seed, pending execution |
|
|
||||||
|
|
||||||
## How To Add The Next Query
|
|
||||||
|
|
||||||
1. Create a new SQL file under `supabase/migrations/`.
|
|
||||||
2. Use the next order number.
|
|
||||||
3. Add a documentation file under `docs/database/`.
|
|
||||||
4. Register both files in this table.
|
|
||||||
5. After running the SQL, add an entry to `query-log.md`.
|
|
||||||
|
|
||||||
## Seed Files
|
|
||||||
|
|
||||||
Seed files are optional and should live under `supabase/seeds/`.
|
|
||||||
|
|
||||||
They must also be documented and registered in this file, but they should only be run in local/demo environments unless explicitly approved.
|
|
||||||
|
|
||||||
## Applying The Ordered SQL
|
|
||||||
|
|
||||||
For a fresh Supabase project, use the one-shot setup file from the repository root:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
psql "postgresql://postgres:[PASSWORD]@[HOST]:5432/postgres" -v ON_ERROR_STOP=1 -f supabase/setup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
The setup file applies `0001` through `0011` in the order listed above. It does not include optional seed data. If you are changing the schema later, keep adding ordered files under `supabase/migrations/` and update this table.
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Seed 0001 Demo Freelancer OS Data
|
|
||||||
|
|
||||||
SQL file: `supabase/seeds/0001_demo_freelancer_os_data.sql`
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Adds demo data for local development and dashboard testing after the Freelancer OS core tables are created.
|
|
||||||
|
|
||||||
## What It Inserts
|
|
||||||
|
|
||||||
- demo clients
|
|
||||||
- demo projects and one side project
|
|
||||||
- demo tasks
|
|
||||||
- demo calendar events
|
|
||||||
- demo finance transactions
|
|
||||||
- demo daily mood/energy logs
|
|
||||||
- demo `app_settings`
|
|
||||||
|
|
||||||
## Required Manual Step
|
|
||||||
|
|
||||||
Before running the file, replace this placeholder with a real `auth.users.id`:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
'00000000-0000-0000-0000-000000000000'::uuid
|
|
||||||
```
|
|
||||||
|
|
||||||
Use a user id from your Supabase Auth users table.
|
|
||||||
|
|
||||||
## Environment
|
|
||||||
|
|
||||||
This seed is intended for local and demo environments only.
|
|
||||||
|
|
||||||
Do not run it on production data.
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
Run after:
|
|
||||||
|
|
||||||
1. `supabase/schema.sql`
|
|
||||||
2. `supabase/migrations/0002_add_freelancer_os_core_tables.sql`
|
|
||||||
|
|
||||||
After running it, add an entry to `docs/database/query-log.md`.
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Legacy Supabase archive
|
|
||||||
|
|
||||||
Bu klasör Neta v2'nin eski PostgreSQL şeması, migration'ları ve seed kayıtları için salt tarihsel/import kaynağıdır.
|
|
||||||
|
|
||||||
Self-hosted v3 runtime'ı bu SQL dosyalarını çalıştırmaz; Supabase Auth, Database, Storage veya environment değişkeni kullanmaz. Yeni değişiklikler `server/db/schema` ve `server/db/migrations` altında Drizzle/SQLite migration'ı olarak yapılmalıdır.
|
|
||||||
|
|
||||||
Bu arşivi yalnızca eski bir Supabase instance'ının kolonlarını anlamak ve `neta-supabase-export` bundle'ı hazırlamak için kullanın. Güncel aktarım sözleşmesi ve cutover runbook'u:
|
|
||||||
|
|
||||||
```text
|
|
||||||
docs/self-hosted-redesign/phase-8-import-release.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Arşivdeki SQL'i yeni Neta kurulumu için çalıştırmayın.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
-- 0002: Add Freelancer OS core tables
|
|
||||||
-- Run after: supabase/schema.sql
|
|
||||||
|
|
||||||
create extension if not exists "uuid-ossp";
|
|
||||||
|
|
||||||
create or replace function public.set_updated_at()
|
|
||||||
returns trigger as $$
|
|
||||||
begin
|
|
||||||
new.updated_at = timezone('utc'::text, now());
|
|
||||||
return new;
|
|
||||||
end;
|
|
||||||
$$ language plpgsql;
|
|
||||||
|
|
||||||
create table if not exists public.clients (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
name text not null,
|
|
||||||
company_name text,
|
|
||||||
email text,
|
|
||||||
phone text,
|
|
||||||
website text,
|
|
||||||
status text default 'active'::text not null,
|
|
||||||
notes text,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists public.projects (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
client_id uuid references public.clients(id) on delete set null,
|
|
||||||
name text not null,
|
|
||||||
type text default 'client_project'::text not null,
|
|
||||||
description text,
|
|
||||||
status text default 'planning'::text not null,
|
|
||||||
start_date date,
|
|
||||||
due_date date,
|
|
||||||
budget_amount numeric(12, 2),
|
|
||||||
currency text default 'USD'::text not null,
|
|
||||||
progress integer default 0 not null,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists public.calendar_events (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
client_id uuid references public.clients(id) on delete set null,
|
|
||||||
project_id uuid references public.projects(id) on delete set null,
|
|
||||||
task_id uuid references public.tasks(id) on delete set null,
|
|
||||||
title text not null,
|
|
||||||
description text,
|
|
||||||
type text default 'focus'::text not null,
|
|
||||||
starts_at timestamp with time zone not null,
|
|
||||||
ends_at timestamp with time zone,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists public.finance_transactions (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
client_id uuid references public.clients(id) on delete set null,
|
|
||||||
project_id uuid references public.projects(id) on delete set null,
|
|
||||||
type text not null,
|
|
||||||
amount numeric(12, 2) not null,
|
|
||||||
currency text default 'USD'::text not null,
|
|
||||||
transaction_date date default current_date not null,
|
|
||||||
category text,
|
|
||||||
payment_status text default 'planned'::text not null,
|
|
||||||
description text,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists public.daily_logs (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
log_date date default current_date not null,
|
|
||||||
mood_score smallint not null,
|
|
||||||
energy_score smallint not null,
|
|
||||||
work_satisfaction_score smallint,
|
|
||||||
note text,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
unique (user_id, log_date)
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists public.app_settings (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null unique,
|
|
||||||
timezone text default 'UTC'::text not null,
|
|
||||||
currency text default 'USD'::text not null,
|
|
||||||
ai_provider text default 'ollama'::text not null,
|
|
||||||
ai_model text,
|
|
||||||
api_key text,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
alter table public.tasks
|
|
||||||
add column if not exists client_id uuid references public.clients(id) on delete set null,
|
|
||||||
add column if not exists project_id uuid references public.projects(id) on delete set null,
|
|
||||||
add column if not exists priority text default 'medium'::text not null,
|
|
||||||
add column if not exists due_at timestamp with time zone,
|
|
||||||
add column if not exists estimated_minutes integer,
|
|
||||||
add column if not exists actual_minutes integer;
|
|
||||||
|
|
||||||
do $$
|
|
||||||
begin
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'clients_status_check') then
|
|
||||||
alter table public.clients
|
|
||||||
add constraint clients_status_check
|
|
||||||
check (status in ('active', 'paused', 'archived'));
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'projects_type_check') then
|
|
||||||
alter table public.projects
|
|
||||||
add constraint projects_type_check
|
|
||||||
check (type in ('client_project', 'side_project'));
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'projects_status_check') then
|
|
||||||
alter table public.projects
|
|
||||||
add constraint projects_status_check
|
|
||||||
check (status in ('planning', 'active', 'paused', 'completed', 'cancelled'));
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'projects_progress_check') then
|
|
||||||
alter table public.projects
|
|
||||||
add constraint projects_progress_check
|
|
||||||
check (progress >= 0 and progress <= 100);
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'tasks_priority_check') then
|
|
||||||
alter table public.tasks
|
|
||||||
add constraint tasks_priority_check
|
|
||||||
check (priority in ('low', 'medium', 'high', 'urgent'));
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'calendar_events_type_check') then
|
|
||||||
alter table public.calendar_events
|
|
||||||
add constraint calendar_events_type_check
|
|
||||||
check (type in ('meeting', 'focus', 'deadline', 'personal', 'finance'));
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'calendar_events_time_check') then
|
|
||||||
alter table public.calendar_events
|
|
||||||
add constraint calendar_events_time_check
|
|
||||||
check (ends_at is null or ends_at >= starts_at);
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'finance_transactions_type_check') then
|
|
||||||
alter table public.finance_transactions
|
|
||||||
add constraint finance_transactions_type_check
|
|
||||||
check (type in ('income', 'expense'));
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'finance_transactions_amount_check') then
|
|
||||||
alter table public.finance_transactions
|
|
||||||
add constraint finance_transactions_amount_check
|
|
||||||
check (amount >= 0);
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'finance_transactions_payment_status_check') then
|
|
||||||
alter table public.finance_transactions
|
|
||||||
add constraint finance_transactions_payment_status_check
|
|
||||||
check (payment_status in ('planned', 'pending', 'paid', 'cancelled'));
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'daily_logs_mood_score_check') then
|
|
||||||
alter table public.daily_logs
|
|
||||||
add constraint daily_logs_mood_score_check
|
|
||||||
check (mood_score between 1 and 5);
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'daily_logs_energy_score_check') then
|
|
||||||
alter table public.daily_logs
|
|
||||||
add constraint daily_logs_energy_score_check
|
|
||||||
check (energy_score between 1 and 5);
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'daily_logs_work_satisfaction_score_check') then
|
|
||||||
alter table public.daily_logs
|
|
||||||
add constraint daily_logs_work_satisfaction_score_check
|
|
||||||
check (work_satisfaction_score is null or work_satisfaction_score between 1 and 5);
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'app_settings_ai_provider_check') then
|
|
||||||
alter table public.app_settings
|
|
||||||
add constraint app_settings_ai_provider_check
|
|
||||||
check (ai_provider in ('ollama', 'openai', 'gemini', 'groq'));
|
|
||||||
end if;
|
|
||||||
end $$;
|
|
||||||
|
|
||||||
create index if not exists clients_user_id_idx on public.clients(user_id);
|
|
||||||
create index if not exists clients_status_idx on public.clients(status);
|
|
||||||
|
|
||||||
create index if not exists projects_user_id_idx on public.projects(user_id);
|
|
||||||
create index if not exists projects_client_id_idx on public.projects(client_id);
|
|
||||||
create index if not exists projects_status_idx on public.projects(status);
|
|
||||||
create index if not exists projects_due_date_idx on public.projects(due_date);
|
|
||||||
|
|
||||||
create index if not exists tasks_client_id_idx on public.tasks(client_id);
|
|
||||||
create index if not exists tasks_project_id_idx on public.tasks(project_id);
|
|
||||||
create index if not exists tasks_due_at_idx on public.tasks(due_at);
|
|
||||||
create index if not exists tasks_priority_idx on public.tasks(priority);
|
|
||||||
|
|
||||||
create index if not exists calendar_events_user_id_idx on public.calendar_events(user_id);
|
|
||||||
create index if not exists calendar_events_starts_at_idx on public.calendar_events(starts_at);
|
|
||||||
create index if not exists calendar_events_project_id_idx on public.calendar_events(project_id);
|
|
||||||
create index if not exists calendar_events_task_id_idx on public.calendar_events(task_id);
|
|
||||||
|
|
||||||
create index if not exists finance_transactions_user_id_idx on public.finance_transactions(user_id);
|
|
||||||
create index if not exists finance_transactions_client_id_idx on public.finance_transactions(client_id);
|
|
||||||
create index if not exists finance_transactions_project_id_idx on public.finance_transactions(project_id);
|
|
||||||
create index if not exists finance_transactions_transaction_date_idx on public.finance_transactions(transaction_date);
|
|
||||||
create index if not exists finance_transactions_payment_status_idx on public.finance_transactions(payment_status);
|
|
||||||
|
|
||||||
create index if not exists daily_logs_user_id_idx on public.daily_logs(user_id);
|
|
||||||
create index if not exists daily_logs_log_date_idx on public.daily_logs(log_date);
|
|
||||||
|
|
||||||
alter table public.clients enable row level security;
|
|
||||||
alter table public.projects enable row level security;
|
|
||||||
alter table public.calendar_events enable row level security;
|
|
||||||
alter table public.finance_transactions enable row level security;
|
|
||||||
alter table public.daily_logs enable row level security;
|
|
||||||
alter table public.app_settings enable row level security;
|
|
||||||
|
|
||||||
drop policy if exists "Users can view their own clients." on public.clients;
|
|
||||||
create policy "Users can view their own clients." on public.clients
|
|
||||||
for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own clients." on public.clients;
|
|
||||||
create policy "Users can insert their own clients." on public.clients
|
|
||||||
for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own clients." on public.clients;
|
|
||||||
create policy "Users can update their own clients." on public.clients
|
|
||||||
for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own clients." on public.clients;
|
|
||||||
create policy "Users can delete their own clients." on public.clients
|
|
||||||
for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can view their own projects." on public.projects;
|
|
||||||
create policy "Users can view their own projects." on public.projects
|
|
||||||
for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own projects." on public.projects;
|
|
||||||
create policy "Users can insert their own projects." on public.projects
|
|
||||||
for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own projects." on public.projects;
|
|
||||||
create policy "Users can update their own projects." on public.projects
|
|
||||||
for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own projects." on public.projects;
|
|
||||||
create policy "Users can delete their own projects." on public.projects
|
|
||||||
for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can view their own calendar events." on public.calendar_events;
|
|
||||||
create policy "Users can view their own calendar events." on public.calendar_events
|
|
||||||
for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own calendar events." on public.calendar_events;
|
|
||||||
create policy "Users can insert their own calendar events." on public.calendar_events
|
|
||||||
for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own calendar events." on public.calendar_events;
|
|
||||||
create policy "Users can update their own calendar events." on public.calendar_events
|
|
||||||
for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own calendar events." on public.calendar_events;
|
|
||||||
create policy "Users can delete their own calendar events." on public.calendar_events
|
|
||||||
for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can view their own finance transactions." on public.finance_transactions;
|
|
||||||
create policy "Users can view their own finance transactions." on public.finance_transactions
|
|
||||||
for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own finance transactions." on public.finance_transactions;
|
|
||||||
create policy "Users can insert their own finance transactions." on public.finance_transactions
|
|
||||||
for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own finance transactions." on public.finance_transactions;
|
|
||||||
create policy "Users can update their own finance transactions." on public.finance_transactions
|
|
||||||
for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own finance transactions." on public.finance_transactions;
|
|
||||||
create policy "Users can delete their own finance transactions." on public.finance_transactions
|
|
||||||
for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can view their own daily logs." on public.daily_logs;
|
|
||||||
create policy "Users can view their own daily logs." on public.daily_logs
|
|
||||||
for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own daily logs." on public.daily_logs;
|
|
||||||
create policy "Users can insert their own daily logs." on public.daily_logs
|
|
||||||
for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own daily logs." on public.daily_logs;
|
|
||||||
create policy "Users can update their own daily logs." on public.daily_logs
|
|
||||||
for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own daily logs." on public.daily_logs;
|
|
||||||
create policy "Users can delete their own daily logs." on public.daily_logs
|
|
||||||
for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can view their own app settings." on public.app_settings;
|
|
||||||
create policy "Users can view their own app settings." on public.app_settings
|
|
||||||
for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own app settings." on public.app_settings;
|
|
||||||
create policy "Users can insert their own app settings." on public.app_settings
|
|
||||||
for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own app settings." on public.app_settings;
|
|
||||||
create policy "Users can update their own app settings." on public.app_settings
|
|
||||||
for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own app settings." on public.app_settings;
|
|
||||||
create policy "Users can delete their own app settings." on public.app_settings
|
|
||||||
for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop trigger if exists set_clients_updated_at on public.clients;
|
|
||||||
create trigger set_clients_updated_at
|
|
||||||
before update on public.clients
|
|
||||||
for each row execute procedure public.set_updated_at();
|
|
||||||
|
|
||||||
drop trigger if exists set_projects_updated_at on public.projects;
|
|
||||||
create trigger set_projects_updated_at
|
|
||||||
before update on public.projects
|
|
||||||
for each row execute procedure public.set_updated_at();
|
|
||||||
|
|
||||||
drop trigger if exists set_calendar_events_updated_at on public.calendar_events;
|
|
||||||
create trigger set_calendar_events_updated_at
|
|
||||||
before update on public.calendar_events
|
|
||||||
for each row execute procedure public.set_updated_at();
|
|
||||||
|
|
||||||
drop trigger if exists set_finance_transactions_updated_at on public.finance_transactions;
|
|
||||||
create trigger set_finance_transactions_updated_at
|
|
||||||
before update on public.finance_transactions
|
|
||||||
for each row execute procedure public.set_updated_at();
|
|
||||||
|
|
||||||
drop trigger if exists set_daily_logs_updated_at on public.daily_logs;
|
|
||||||
create trigger set_daily_logs_updated_at
|
|
||||||
before update on public.daily_logs
|
|
||||||
for each row execute procedure public.set_updated_at();
|
|
||||||
|
|
||||||
drop trigger if exists set_app_settings_updated_at on public.app_settings;
|
|
||||||
create trigger set_app_settings_updated_at
|
|
||||||
before update on public.app_settings
|
|
||||||
for each row execute procedure public.set_updated_at();
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
-- 0003: Add project planning sections and project visual storage
|
|
||||||
-- Run after: supabase/migrations/0002_add_freelancer_os_core_tables.sql
|
|
||||||
|
|
||||||
alter table public.projects
|
|
||||||
add column if not exists cover_image_path text,
|
|
||||||
add column if not exists cover_image_alt text;
|
|
||||||
|
|
||||||
create table if not exists public.project_planning_sections (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
project_id uuid references public.projects(id) on delete cascade not null,
|
|
||||||
category text not null,
|
|
||||||
title text not null,
|
|
||||||
content text,
|
|
||||||
metadata jsonb default '{}'::jsonb not null,
|
|
||||||
sort_order integer default 0 not null,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
do $$
|
|
||||||
begin
|
|
||||||
if not exists (select 1 from pg_constraint where conname = 'project_planning_sections_category_check') then
|
|
||||||
alter table public.project_planning_sections
|
|
||||||
add constraint project_planning_sections_category_check
|
|
||||||
check (
|
|
||||||
category in (
|
|
||||||
'overview',
|
|
||||||
'problem',
|
|
||||||
'goal',
|
|
||||||
'audience',
|
|
||||||
'scope',
|
|
||||||
'design_system',
|
|
||||||
'color_palette',
|
|
||||||
'typography',
|
|
||||||
'assets',
|
|
||||||
'notes'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
end if;
|
|
||||||
end $$;
|
|
||||||
|
|
||||||
create index if not exists project_planning_sections_user_id_idx
|
|
||||||
on public.project_planning_sections(user_id);
|
|
||||||
|
|
||||||
create index if not exists project_planning_sections_project_id_idx
|
|
||||||
on public.project_planning_sections(project_id);
|
|
||||||
|
|
||||||
create index if not exists project_planning_sections_category_idx
|
|
||||||
on public.project_planning_sections(category);
|
|
||||||
|
|
||||||
alter table public.project_planning_sections enable row level security;
|
|
||||||
|
|
||||||
drop policy if exists "Users can view their own project planning sections." on public.project_planning_sections;
|
|
||||||
create policy "Users can view their own project planning sections." on public.project_planning_sections
|
|
||||||
for select using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can insert their own project planning sections." on public.project_planning_sections;
|
|
||||||
create policy "Users can insert their own project planning sections." on public.project_planning_sections
|
|
||||||
for insert with check (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can update their own project planning sections." on public.project_planning_sections;
|
|
||||||
create policy "Users can update their own project planning sections." on public.project_planning_sections
|
|
||||||
for update using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own project planning sections." on public.project_planning_sections;
|
|
||||||
create policy "Users can delete their own project planning sections." on public.project_planning_sections
|
|
||||||
for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop trigger if exists set_project_planning_sections_updated_at on public.project_planning_sections;
|
|
||||||
create trigger set_project_planning_sections_updated_at
|
|
||||||
before update on public.project_planning_sections
|
|
||||||
for each row execute procedure public.set_updated_at();
|
|
||||||
|
|
||||||
insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
|
||||||
values (
|
|
||||||
'project-assets',
|
|
||||||
'project-assets',
|
|
||||||
false,
|
|
||||||
5242880,
|
|
||||||
array['image/jpeg', 'image/png', 'image/webp', 'image/gif']
|
|
||||||
)
|
|
||||||
on conflict (id) do update set
|
|
||||||
public = excluded.public,
|
|
||||||
file_size_limit = excluded.file_size_limit,
|
|
||||||
allowed_mime_types = excluded.allowed_mime_types;
|
|
||||||
|
|
||||||
drop policy if exists "Users can view their own project assets." on storage.objects;
|
|
||||||
create policy "Users can view their own project assets." on storage.objects
|
|
||||||
for select using (
|
|
||||||
bucket_id = 'project-assets'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can upload their own project assets." on storage.objects;
|
|
||||||
create policy "Users can upload their own project assets." on storage.objects
|
|
||||||
for insert with check (
|
|
||||||
bucket_id = 'project-assets'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can update their own project assets." on storage.objects;
|
|
||||||
create policy "Users can update their own project assets." on storage.objects
|
|
||||||
for update using (
|
|
||||||
bucket_id = 'project-assets'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own project assets." on storage.objects;
|
|
||||||
create policy "Users can delete their own project assets." on storage.objects
|
|
||||||
for delete using (
|
|
||||||
bucket_id = 'project-assets'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
-- 0004: Faz 6 - Freelancer Business OS Tables
|
|
||||||
|
|
||||||
-- 1. Proposals
|
|
||||||
create table if not exists public.proposals (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
client_id uuid references public.clients(id) on delete set null,
|
|
||||||
project_id uuid references public.projects(id) on delete set null,
|
|
||||||
title text not null,
|
|
||||||
description text,
|
|
||||||
amount numeric(12,2) not null default 0,
|
|
||||||
currency text default 'TRY'::text,
|
|
||||||
status text default 'draft'::text check (status in ('draft', 'sent', 'accepted', 'rejected')),
|
|
||||||
valid_until timestamp with time zone,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 2. Contracts
|
|
||||||
create table if not exists public.contracts (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
proposal_id uuid references public.proposals(id) on delete set null,
|
|
||||||
client_id uuid references public.clients(id) on delete set null,
|
|
||||||
title text not null,
|
|
||||||
content text,
|
|
||||||
status text default 'draft'::text check (status in ('draft', 'active', 'completed', 'cancelled')),
|
|
||||||
signed_at timestamp with time zone,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 3. Invoices
|
|
||||||
create table if not exists public.invoices (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
client_id uuid references public.clients(id) on delete set null,
|
|
||||||
project_id uuid references public.projects(id) on delete set null,
|
|
||||||
invoice_number text not null,
|
|
||||||
amount numeric(12,2) not null default 0,
|
|
||||||
tax_rate numeric(5,2) default 0, -- percentage
|
|
||||||
currency text default 'TRY'::text,
|
|
||||||
status text default 'draft'::text check (status in ('draft', 'sent', 'paid', 'overdue', 'cancelled')),
|
|
||||||
issue_date timestamp with time zone default timezone('utc'::text, now()),
|
|
||||||
due_date timestamp with time zone,
|
|
||||||
paid_at timestamp with time zone,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 4. Subscriptions
|
|
||||||
create table if not exists public.subscriptions (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
name text not null,
|
|
||||||
amount numeric(12,2) not null default 0,
|
|
||||||
currency text default 'TRY'::text,
|
|
||||||
billing_cycle text default 'monthly'::text check (billing_cycle in ('monthly', 'yearly', 'weekly')),
|
|
||||||
next_billing_date timestamp with time zone,
|
|
||||||
status text default 'active'::text check (status in ('active', 'cancelled')),
|
|
||||||
category text, -- e.g., 'software', 'hosting', 'marketing'
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Enable RLS
|
|
||||||
alter table public.proposals enable row level security;
|
|
||||||
alter table public.contracts enable row level security;
|
|
||||||
alter table public.invoices enable row level security;
|
|
||||||
alter table public.subscriptions enable row level security;
|
|
||||||
|
|
||||||
-- Proposals RLS
|
|
||||||
drop policy if exists "Users can view their own proposals" on public.proposals;
|
|
||||||
create policy "Users can view their own proposals" on public.proposals for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own proposals" on public.proposals;
|
|
||||||
create policy "Users can insert their own proposals" on public.proposals for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own proposals" on public.proposals;
|
|
||||||
create policy "Users can update their own proposals" on public.proposals for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own proposals" on public.proposals;
|
|
||||||
create policy "Users can delete their own proposals" on public.proposals for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
-- Contracts RLS
|
|
||||||
drop policy if exists "Users can view their own contracts" on public.contracts;
|
|
||||||
create policy "Users can view their own contracts" on public.contracts for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own contracts" on public.contracts;
|
|
||||||
create policy "Users can insert their own contracts" on public.contracts for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own contracts" on public.contracts;
|
|
||||||
create policy "Users can update their own contracts" on public.contracts for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own contracts" on public.contracts;
|
|
||||||
create policy "Users can delete their own contracts" on public.contracts for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
-- Invoices RLS
|
|
||||||
drop policy if exists "Users can view their own invoices" on public.invoices;
|
|
||||||
create policy "Users can view their own invoices" on public.invoices for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own invoices" on public.invoices;
|
|
||||||
create policy "Users can insert their own invoices" on public.invoices for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own invoices" on public.invoices;
|
|
||||||
create policy "Users can update their own invoices" on public.invoices for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own invoices" on public.invoices;
|
|
||||||
create policy "Users can delete their own invoices" on public.invoices for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
-- Subscriptions RLS
|
|
||||||
drop policy if exists "Users can view their own subscriptions" on public.subscriptions;
|
|
||||||
create policy "Users can view their own subscriptions" on public.subscriptions for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own subscriptions" on public.subscriptions;
|
|
||||||
create policy "Users can insert their own subscriptions" on public.subscriptions for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own subscriptions" on public.subscriptions;
|
|
||||||
create policy "Users can update their own subscriptions" on public.subscriptions for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own subscriptions" on public.subscriptions;
|
|
||||||
create policy "Users can delete their own subscriptions" on public.subscriptions for delete using (auth.uid() = user_id);
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
-- 0005: Faz 7 - Advanced CRM Tables
|
|
||||||
|
|
||||||
-- 1. Alter clients table to add CRM specific columns
|
|
||||||
alter table public.clients
|
|
||||||
add column if not exists pipeline_stage text default 'lead'::text check (pipeline_stage in ('lead', 'contacted', 'proposal_sent', 'won', 'lost')),
|
|
||||||
add column if not exists next_follow_up_date timestamp with time zone,
|
|
||||||
add column if not exists last_contact_date timestamp with time zone,
|
|
||||||
add column if not exists client_value_score numeric(5,2) default 0;
|
|
||||||
|
|
||||||
-- 2. Create client_activities table
|
|
||||||
create table if not exists public.client_activities (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
client_id uuid references public.clients(id) on delete cascade not null,
|
|
||||||
type text not null check (type in ('note', 'call', 'meeting', 'email')),
|
|
||||||
title text not null,
|
|
||||||
content text,
|
|
||||||
activity_date timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Enable RLS
|
|
||||||
alter table public.client_activities enable row level security;
|
|
||||||
|
|
||||||
-- Client Activities RLS
|
|
||||||
drop policy if exists "Users can view their own client activities" on public.client_activities;
|
|
||||||
create policy "Users can view their own client activities" on public.client_activities for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own client activities" on public.client_activities;
|
|
||||||
create policy "Users can insert their own client activities" on public.client_activities for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own client activities" on public.client_activities;
|
|
||||||
create policy "Users can update their own client activities" on public.client_activities for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own client activities" on public.client_activities;
|
|
||||||
create policy "Users can delete their own client activities" on public.client_activities for delete using (auth.uid() = user_id);
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
-- 0006: Faz 8 - pgvector & RAG Embeddings
|
|
||||||
|
|
||||||
-- Enable the pgvector extension to work with embedding vectors
|
|
||||||
create extension if not exists vector;
|
|
||||||
|
|
||||||
-- Create a table to store document embeddings for RAG
|
|
||||||
create table if not exists public.document_embeddings (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
content text not null,
|
|
||||||
metadata jsonb, -- e.g. { "source_type": "note", "source_id": "123" }
|
|
||||||
embedding vector(1536), -- 1536 works for OpenAI text-embedding-3-small and text-embedding-ada-002
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Enable RLS
|
|
||||||
alter table public.document_embeddings enable row level security;
|
|
||||||
|
|
||||||
drop policy if exists "Users can view their own embeddings" on public.document_embeddings;
|
|
||||||
create policy "Users can view their own embeddings" on public.document_embeddings for select using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can insert their own embeddings" on public.document_embeddings;
|
|
||||||
create policy "Users can insert their own embeddings" on public.document_embeddings for insert with check (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can update their own embeddings" on public.document_embeddings;
|
|
||||||
create policy "Users can update their own embeddings" on public.document_embeddings for update using (auth.uid() = user_id);
|
|
||||||
drop policy if exists "Users can delete their own embeddings" on public.document_embeddings;
|
|
||||||
create policy "Users can delete their own embeddings" on public.document_embeddings for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
-- Create a function to similarity search for embeddings
|
|
||||||
create or replace function match_documents (
|
|
||||||
query_embedding vector(1536),
|
|
||||||
match_count int default null,
|
|
||||||
filter_user_id uuid default null
|
|
||||||
) returns table (
|
|
||||||
id uuid,
|
|
||||||
content text,
|
|
||||||
metadata jsonb,
|
|
||||||
similarity float
|
|
||||||
)
|
|
||||||
language plpgsql
|
|
||||||
as $$
|
|
||||||
#variable_conflict use_column
|
|
||||||
begin
|
|
||||||
return query
|
|
||||||
select
|
|
||||||
document_embeddings.id,
|
|
||||||
document_embeddings.content,
|
|
||||||
document_embeddings.metadata,
|
|
||||||
1 - (document_embeddings.embedding <=> query_embedding) as similarity
|
|
||||||
from document_embeddings
|
|
||||||
where document_embeddings.user_id = filter_user_id
|
|
||||||
order by document_embeddings.embedding <=> query_embedding
|
|
||||||
limit match_count;
|
|
||||||
end;
|
|
||||||
$$;
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
-- 0007: Add Client Portal Tables and Roles
|
|
||||||
-- Run after: 0006_add_pgvector_and_embeddings.sql
|
|
||||||
|
|
||||||
-- 1. Update Profiles with Role
|
|
||||||
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS role text DEFAULT 'freelancer'::text CHECK (role IN ('freelancer', 'client'));
|
|
||||||
|
|
||||||
-- 2. Update Clients to link to Client Auth ID
|
|
||||||
ALTER TABLE public.clients ADD COLUMN IF NOT EXISTS client_auth_id uuid REFERENCES auth.users(id) ON DELETE SET NULL;
|
|
||||||
|
|
||||||
-- 3. Update Tasks to add Client Visibility
|
|
||||||
ALTER TABLE public.tasks ADD COLUMN IF NOT EXISTS is_public_to_client boolean DEFAULT false;
|
|
||||||
|
|
||||||
-- 4. Create Project Revisions table
|
|
||||||
CREATE TABLE IF NOT EXISTS public.project_revisions (
|
|
||||||
id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
||||||
project_id uuid REFERENCES public.projects(id) ON DELETE CASCADE NOT NULL,
|
|
||||||
client_id uuid REFERENCES public.clients(id) ON DELETE CASCADE NOT NULL,
|
|
||||||
requested_by uuid REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
|
|
||||||
description text NOT NULL,
|
|
||||||
status text DEFAULT 'pending'::text CHECK (status IN ('pending', 'in_progress', 'completed', 'rejected')),
|
|
||||||
created_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
|
|
||||||
updated_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Trigger to set updated_at
|
|
||||||
DROP TRIGGER IF EXISTS set_project_revisions_updated_at ON public.project_revisions;
|
|
||||||
CREATE TRIGGER set_project_revisions_updated_at
|
|
||||||
BEFORE UPDATE ON public.project_revisions
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION public.set_updated_at();
|
|
||||||
|
|
||||||
-- 5. Enable RLS
|
|
||||||
ALTER TABLE public.project_revisions ENABLE ROW LEVEL SECURITY;
|
|
||||||
|
|
||||||
-- 6. Add RLS Policies for Client Access
|
|
||||||
|
|
||||||
-- PROFILES
|
|
||||||
-- Clients can read their own profile
|
|
||||||
DROP POLICY IF EXISTS "Clients can view own profile" ON public.profiles;
|
|
||||||
CREATE POLICY "Clients can view own profile"
|
|
||||||
ON public.profiles FOR SELECT
|
|
||||||
USING (auth.uid() = id);
|
|
||||||
|
|
||||||
-- CLIENTS
|
|
||||||
-- Clients can read their own client record
|
|
||||||
DROP POLICY IF EXISTS "Clients can view own client record" ON public.clients;
|
|
||||||
CREATE POLICY "Clients can view own client record"
|
|
||||||
ON public.clients FOR SELECT
|
|
||||||
USING (client_auth_id = auth.uid());
|
|
||||||
|
|
||||||
-- PROJECTS
|
|
||||||
-- Clients can view projects where client_id matches their client record
|
|
||||||
DROP POLICY IF EXISTS "Clients can view own projects" ON public.projects;
|
|
||||||
CREATE POLICY "Clients can view own projects"
|
|
||||||
ON public.projects FOR SELECT
|
|
||||||
USING (
|
|
||||||
client_id IN (
|
|
||||||
SELECT id FROM public.clients WHERE client_auth_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- TASKS
|
|
||||||
-- Clients can view tasks linked to their projects IF is_public_to_client is true
|
|
||||||
DROP POLICY IF EXISTS "Clients can view public tasks of their projects" ON public.tasks;
|
|
||||||
CREATE POLICY "Clients can view public tasks of their projects"
|
|
||||||
ON public.tasks FOR SELECT
|
|
||||||
USING (
|
|
||||||
is_public_to_client = true
|
|
||||||
AND project_id IN (
|
|
||||||
SELECT id FROM public.projects WHERE client_id IN (
|
|
||||||
SELECT id FROM public.clients WHERE client_auth_id = auth.uid()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- PROJECT PLANNING SECTIONS (Milestones, Roadmap, etc.)
|
|
||||||
-- Clients can view planning sections of their projects
|
|
||||||
DROP POLICY IF EXISTS "Clients can view planning sections of their projects" ON public.project_planning_sections;
|
|
||||||
CREATE POLICY "Clients can view planning sections of their projects"
|
|
||||||
ON public.project_planning_sections FOR SELECT
|
|
||||||
USING (
|
|
||||||
project_id IN (
|
|
||||||
SELECT id FROM public.projects WHERE client_id IN (
|
|
||||||
SELECT id FROM public.clients WHERE client_auth_id = auth.uid()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- PROJECT REVISIONS
|
|
||||||
-- Freelancers can manage all revisions for their projects
|
|
||||||
DROP POLICY IF EXISTS "Freelancers can manage revisions" ON public.project_revisions;
|
|
||||||
CREATE POLICY "Freelancers can manage revisions"
|
|
||||||
ON public.project_revisions FOR ALL
|
|
||||||
USING (
|
|
||||||
project_id IN (
|
|
||||||
SELECT id FROM public.projects WHERE user_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Clients can insert revisions for their projects
|
|
||||||
DROP POLICY IF EXISTS "Clients can insert revisions" ON public.project_revisions;
|
|
||||||
CREATE POLICY "Clients can insert revisions"
|
|
||||||
ON public.project_revisions FOR INSERT
|
|
||||||
WITH CHECK (
|
|
||||||
client_id IN (
|
|
||||||
SELECT id FROM public.clients WHERE client_auth_id = auth.uid()
|
|
||||||
)
|
|
||||||
AND requested_by = auth.uid()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Clients can view their own revisions
|
|
||||||
DROP POLICY IF EXISTS "Clients can view own revisions" ON public.project_revisions;
|
|
||||||
CREATE POLICY "Clients can view own revisions"
|
|
||||||
ON public.project_revisions FOR SELECT
|
|
||||||
USING (
|
|
||||||
client_id IN (
|
|
||||||
SELECT id FROM public.clients WHERE client_auth_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
-- 0008: Add Project Progress Type and Revision Quota
|
|
||||||
|
|
||||||
-- Add columns to projects table
|
|
||||||
ALTER TABLE public.projects
|
|
||||||
ADD COLUMN IF NOT EXISTS progress_type text DEFAULT 'manual'::text CHECK (progress_type IN ('manual', 'auto')),
|
|
||||||
ADD COLUMN IF NOT EXISTS revision_quota integer DEFAULT 0;
|
|
||||||
|
|
||||||
-- Function to update project progress automatically if progress_type is 'auto'
|
|
||||||
CREATE OR REPLACE FUNCTION public.update_project_progress_on_task_change()
|
|
||||||
RETURNS TRIGGER AS $$
|
|
||||||
DECLARE
|
|
||||||
v_project_id uuid;
|
|
||||||
v_progress_type text;
|
|
||||||
v_total_tasks integer;
|
|
||||||
v_done_tasks integer;
|
|
||||||
v_new_progress integer;
|
|
||||||
BEGIN
|
|
||||||
IF TG_OP = 'DELETE' THEN
|
|
||||||
v_project_id := OLD.project_id;
|
|
||||||
ELSE
|
|
||||||
v_project_id := NEW.project_id;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
IF v_project_id IS NULL THEN
|
|
||||||
RETURN NULL;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
SELECT progress_type INTO v_progress_type FROM public.projects WHERE id = v_project_id;
|
|
||||||
|
|
||||||
IF v_progress_type = 'auto' THEN
|
|
||||||
SELECT count(*) INTO v_total_tasks FROM public.tasks WHERE project_id = v_project_id;
|
|
||||||
SELECT count(*) INTO v_done_tasks FROM public.tasks WHERE project_id = v_project_id AND status = 'done';
|
|
||||||
|
|
||||||
IF v_total_tasks > 0 THEN
|
|
||||||
v_new_progress := round((v_done_tasks::numeric / v_total_tasks::numeric) * 100);
|
|
||||||
ELSE
|
|
||||||
v_new_progress := 0;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
UPDATE public.projects SET progress = v_new_progress WHERE id = v_project_id;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
RETURN NULL;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
||||||
|
|
||||||
-- Trigger to recalculate progress when tasks are modified
|
|
||||||
DROP TRIGGER IF EXISTS trigger_update_project_progress ON public.tasks;
|
|
||||||
CREATE TRIGGER trigger_update_project_progress
|
|
||||||
AFTER INSERT OR UPDATE OF status OR DELETE
|
|
||||||
ON public.tasks
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION public.update_project_progress_on_task_change();
|
|
||||||
|
|
||||||
-- Trigger to recalculate progress when a project's progress_type is changed to 'auto'
|
|
||||||
CREATE OR REPLACE FUNCTION public.update_project_progress_on_type_change()
|
|
||||||
RETURNS TRIGGER AS $$
|
|
||||||
DECLARE
|
|
||||||
v_total_tasks integer;
|
|
||||||
v_done_tasks integer;
|
|
||||||
v_new_progress integer;
|
|
||||||
BEGIN
|
|
||||||
IF NEW.progress_type = 'auto' AND (OLD.progress_type IS DISTINCT FROM NEW.progress_type) THEN
|
|
||||||
SELECT count(*) INTO v_total_tasks FROM public.tasks WHERE project_id = NEW.id;
|
|
||||||
SELECT count(*) INTO v_done_tasks FROM public.tasks WHERE project_id = NEW.id AND status = 'done';
|
|
||||||
|
|
||||||
IF v_total_tasks > 0 THEN
|
|
||||||
v_new_progress := round((v_done_tasks::numeric / v_total_tasks::numeric) * 100);
|
|
||||||
ELSE
|
|
||||||
v_new_progress := 0;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
NEW.progress := v_new_progress;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
||||||
|
|
||||||
DROP TRIGGER IF EXISTS trigger_update_project_progress_type ON public.projects;
|
|
||||||
CREATE TRIGGER trigger_update_project_progress_type
|
|
||||||
BEFORE UPDATE OF progress_type
|
|
||||||
ON public.projects
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION public.update_project_progress_on_type_change();
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
-- 0009: Lock public registration after the first self-host admin account
|
|
||||||
-- Run after: supabase/migrations/0008_add_project_progress_and_quota.sql
|
|
||||||
|
|
||||||
create or replace function public.is_first_admin_setup_available()
|
|
||||||
returns boolean
|
|
||||||
language sql
|
|
||||||
security definer
|
|
||||||
set search_path = public
|
|
||||||
as $$
|
|
||||||
select not exists (
|
|
||||||
select 1
|
|
||||||
from public.profiles
|
|
||||||
limit 1
|
|
||||||
);
|
|
||||||
$$;
|
|
||||||
|
|
||||||
revoke all on function public.is_first_admin_setup_available() from public;
|
|
||||||
grant execute on function public.is_first_admin_setup_available() to anon;
|
|
||||||
grant execute on function public.is_first_admin_setup_available() to authenticated;
|
|
||||||
|
|
||||||
create or replace function public.handle_new_user()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql
|
|
||||||
security definer
|
|
||||||
set search_path = public
|
|
||||||
as $$
|
|
||||||
begin
|
|
||||||
if exists (select 1 from public.profiles limit 1)
|
|
||||||
and coalesce(new.raw_app_meta_data->>'internal_created', 'false') <> 'true' then
|
|
||||||
raise exception 'Registration is closed. The first admin account already exists.';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
insert into public.profiles (id, first_name, last_name, avatar_url)
|
|
||||||
values (new.id, '', '', '')
|
|
||||||
on conflict (id) do nothing;
|
|
||||||
|
|
||||||
return new;
|
|
||||||
end;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
drop trigger if exists on_auth_user_created on auth.users;
|
|
||||||
|
|
||||||
create trigger on_auth_user_created
|
|
||||||
after insert on auth.users
|
|
||||||
for each row execute procedure public.handle_new_user();
|
|
||||||
|
|
||||||
notify pgrst, 'reload schema';
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
-- 0010: Allow trusted server-side internal Auth user creation
|
|
||||||
-- Run after: supabase/migrations/0009_lock_registration_after_first_admin.sql
|
|
||||||
|
|
||||||
create schema if not exists neta_internal;
|
|
||||||
|
|
||||||
create table if not exists neta_internal.internal_auth_creations (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
email text not null,
|
|
||||||
reason text default 'internal'::text not null,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
expires_at timestamp with time zone default (timezone('utc'::text, now()) + interval '2 minutes') not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create index if not exists internal_auth_creations_email_idx
|
|
||||||
on neta_internal.internal_auth_creations (lower(email));
|
|
||||||
|
|
||||||
revoke all on schema neta_internal from public;
|
|
||||||
revoke all on all tables in schema neta_internal from public;
|
|
||||||
|
|
||||||
create or replace function public.neta_current_jwt_role()
|
|
||||||
returns text
|
|
||||||
language sql
|
|
||||||
stable
|
|
||||||
as $$
|
|
||||||
select coalesce(
|
|
||||||
nullif(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role', ''),
|
|
||||||
nullif(current_setting('request.jwt.claim.role', true), ''),
|
|
||||||
''
|
|
||||||
);
|
|
||||||
$$;
|
|
||||||
|
|
||||||
revoke all on function public.neta_current_jwt_role() from public;
|
|
||||||
grant execute on function public.neta_current_jwt_role() to anon;
|
|
||||||
grant execute on function public.neta_current_jwt_role() to authenticated;
|
|
||||||
grant execute on function public.neta_current_jwt_role() to service_role;
|
|
||||||
|
|
||||||
create or replace function public.request_internal_auth_creation(
|
|
||||||
target_email text,
|
|
||||||
target_reason text default 'internal'
|
|
||||||
)
|
|
||||||
returns void
|
|
||||||
language plpgsql
|
|
||||||
security definer
|
|
||||||
set search_path = public, neta_internal
|
|
||||||
as $$
|
|
||||||
begin
|
|
||||||
if public.neta_current_jwt_role() <> 'service_role' then
|
|
||||||
raise exception 'Only service role can request internal auth creation.';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if target_email is null or btrim(target_email) = '' then
|
|
||||||
raise exception 'target_email is required.';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
delete from neta_internal.internal_auth_creations
|
|
||||||
where expires_at <= timezone('utc'::text, now())
|
|
||||||
or lower(email) = lower(btrim(target_email));
|
|
||||||
|
|
||||||
insert into neta_internal.internal_auth_creations (email, reason)
|
|
||||||
values (btrim(target_email), coalesce(nullif(btrim(target_reason), ''), 'internal'));
|
|
||||||
end;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
revoke all on function public.request_internal_auth_creation(text, text) from public;
|
|
||||||
grant execute on function public.request_internal_auth_creation(text, text) to service_role;
|
|
||||||
|
|
||||||
create or replace function public.handle_new_user()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql
|
|
||||||
security definer
|
|
||||||
set search_path = public, neta_internal
|
|
||||||
as $$
|
|
||||||
declare
|
|
||||||
allowed_internal_creation boolean := false;
|
|
||||||
begin
|
|
||||||
delete from neta_internal.internal_auth_creations
|
|
||||||
where lower(email) = lower(new.email)
|
|
||||||
and expires_at > timezone('utc'::text, now())
|
|
||||||
returning true into allowed_internal_creation;
|
|
||||||
|
|
||||||
allowed_internal_creation := coalesce(allowed_internal_creation, false);
|
|
||||||
|
|
||||||
if exists (select 1 from public.profiles limit 1)
|
|
||||||
and coalesce(new.raw_app_meta_data->>'internal_created', 'false') <> 'true'
|
|
||||||
and not allowed_internal_creation then
|
|
||||||
raise exception 'Registration is closed. The first admin account already exists.';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
insert into public.profiles (id, first_name, last_name, avatar_url)
|
|
||||||
values (new.id, '', '', '')
|
|
||||||
on conflict (id) do nothing;
|
|
||||||
|
|
||||||
return new;
|
|
||||||
end;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
drop trigger if exists on_auth_user_created on auth.users;
|
|
||||||
|
|
||||||
create trigger on_auth_user_created
|
|
||||||
after insert on auth.users
|
|
||||||
for each row execute procedure public.handle_new_user();
|
|
||||||
|
|
||||||
notify pgrst, 'reload schema';
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
-- 0011: Fix service-role JWT claim handling and storage policies
|
|
||||||
-- Run after: supabase/migrations/0010_allow_internal_auth_user_creation.sql
|
|
||||||
|
|
||||||
create schema if not exists neta_internal;
|
|
||||||
|
|
||||||
create table if not exists neta_internal.internal_auth_creations (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
email text not null,
|
|
||||||
reason text default 'internal'::text not null,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
expires_at timestamp with time zone default (timezone('utc'::text, now()) + interval '2 minutes') not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create index if not exists internal_auth_creations_email_idx
|
|
||||||
on neta_internal.internal_auth_creations (lower(email));
|
|
||||||
|
|
||||||
revoke all on schema neta_internal from public;
|
|
||||||
revoke all on all tables in schema neta_internal from public;
|
|
||||||
|
|
||||||
create or replace function public.neta_current_jwt_role()
|
|
||||||
returns text
|
|
||||||
language sql
|
|
||||||
stable
|
|
||||||
as $$
|
|
||||||
select coalesce(
|
|
||||||
nullif(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role', ''),
|
|
||||||
nullif(current_setting('request.jwt.claim.role', true), ''),
|
|
||||||
''
|
|
||||||
);
|
|
||||||
$$;
|
|
||||||
|
|
||||||
revoke all on function public.neta_current_jwt_role() from public;
|
|
||||||
grant execute on function public.neta_current_jwt_role() to anon;
|
|
||||||
grant execute on function public.neta_current_jwt_role() to authenticated;
|
|
||||||
grant execute on function public.neta_current_jwt_role() to service_role;
|
|
||||||
|
|
||||||
create or replace function public.request_internal_auth_creation(
|
|
||||||
target_email text,
|
|
||||||
target_reason text default 'internal'
|
|
||||||
)
|
|
||||||
returns void
|
|
||||||
language plpgsql
|
|
||||||
security definer
|
|
||||||
set search_path = public, neta_internal
|
|
||||||
as $$
|
|
||||||
begin
|
|
||||||
if public.neta_current_jwt_role() <> 'service_role' then
|
|
||||||
raise exception 'Only service role can request internal auth creation.';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if target_email is null or btrim(target_email) = '' then
|
|
||||||
raise exception 'target_email is required.';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
delete from neta_internal.internal_auth_creations
|
|
||||||
where expires_at <= timezone('utc'::text, now())
|
|
||||||
or lower(email) = lower(btrim(target_email));
|
|
||||||
|
|
||||||
insert into neta_internal.internal_auth_creations (email, reason)
|
|
||||||
values (btrim(target_email), coalesce(nullif(btrim(target_reason), ''), 'internal'));
|
|
||||||
end;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
revoke all on function public.request_internal_auth_creation(text, text) from public;
|
|
||||||
grant execute on function public.request_internal_auth_creation(text, text) to service_role;
|
|
||||||
|
|
||||||
drop policy if exists "Avatar images are publicly accessible." on storage.objects;
|
|
||||||
create policy "Avatar images are publicly accessible."
|
|
||||||
on storage.objects for select
|
|
||||||
using (
|
|
||||||
bucket_id = 'avatars'
|
|
||||||
or public.neta_current_jwt_role() = 'service_role'
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can upload an avatar." on storage.objects;
|
|
||||||
create policy "Users can upload an avatar."
|
|
||||||
on storage.objects for insert
|
|
||||||
with check (
|
|
||||||
bucket_id = 'avatars'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can update their own avatar." on storage.objects;
|
|
||||||
create policy "Users can update their own avatar."
|
|
||||||
on storage.objects for update
|
|
||||||
using (
|
|
||||||
bucket_id = 'avatars'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own avatar." on storage.objects;
|
|
||||||
create policy "Users can delete their own avatar."
|
|
||||||
on storage.objects for delete
|
|
||||||
using (
|
|
||||||
bucket_id = 'avatars'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can view their own project assets." on storage.objects;
|
|
||||||
create policy "Users can view their own project assets." on storage.objects
|
|
||||||
for select using (
|
|
||||||
bucket_id = 'project-assets'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can upload their own project assets." on storage.objects;
|
|
||||||
create policy "Users can upload their own project assets." on storage.objects
|
|
||||||
for insert with check (
|
|
||||||
bucket_id = 'project-assets'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can update their own project assets." on storage.objects;
|
|
||||||
create policy "Users can update their own project assets." on storage.objects
|
|
||||||
for update using (
|
|
||||||
bucket_id = 'project-assets'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own project assets." on storage.objects;
|
|
||||||
create policy "Users can delete their own project assets." on storage.objects
|
|
||||||
for delete using (
|
|
||||||
bucket_id = 'project-assets'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
notify pgrst, 'reload schema';
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
-- 0012_add_analytics_rpcs.sql
|
|
||||||
-- RPCs for Dashboard and Analytics aggregations to optimize frontend payload size
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION public.get_dashboard_metrics(
|
|
||||||
p_start_date TIMESTAMP WITH TIME ZONE,
|
|
||||||
p_end_date TIMESTAMP WITH TIME ZONE
|
|
||||||
)
|
|
||||||
RETURNS JSONB
|
|
||||||
LANGUAGE plpgsql
|
|
||||||
SECURITY DEFINER
|
|
||||||
SET search_path = public
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
v_user_id UUID := auth.uid();
|
|
||||||
v_income NUMERIC;
|
|
||||||
v_expense NUMERIC;
|
|
||||||
v_net_profit NUMERIC;
|
|
||||||
v_active_projects INT;
|
|
||||||
v_completed_tasks INT;
|
|
||||||
v_avg_mood NUMERIC;
|
|
||||||
v_finance_trend JSONB;
|
|
||||||
v_mood_trend JSONB;
|
|
||||||
BEGIN
|
|
||||||
IF v_user_id IS NULL THEN
|
|
||||||
RAISE EXCEPTION 'Not authenticated';
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- 1. Net Profit
|
|
||||||
SELECT COALESCE(SUM(amount), 0) INTO v_income
|
|
||||||
FROM finance_transactions
|
|
||||||
WHERE user_id = v_user_id AND type = 'income'
|
|
||||||
AND transaction_date >= p_start_date::date AND transaction_date <= p_end_date::date;
|
|
||||||
|
|
||||||
SELECT COALESCE(SUM(amount), 0) INTO v_expense
|
|
||||||
FROM finance_transactions
|
|
||||||
WHERE user_id = v_user_id AND type = 'expense'
|
|
||||||
AND transaction_date >= p_start_date::date AND transaction_date <= p_end_date::date;
|
|
||||||
|
|
||||||
v_net_profit := v_income - v_expense;
|
|
||||||
|
|
||||||
-- 2. Active Projects
|
|
||||||
SELECT COUNT(*) INTO v_active_projects
|
|
||||||
FROM projects
|
|
||||||
WHERE user_id = v_user_id AND status = 'active';
|
|
||||||
|
|
||||||
-- 3. Completed Tasks
|
|
||||||
SELECT COUNT(*) INTO v_completed_tasks
|
|
||||||
FROM tasks
|
|
||||||
WHERE user_id = v_user_id AND status = 'done'
|
|
||||||
AND COALESCE(updated_at, created_at) >= p_start_date AND COALESCE(updated_at, created_at) <= p_end_date;
|
|
||||||
|
|
||||||
-- 4. Average Mood
|
|
||||||
SELECT COALESCE(ROUND(AVG(mood_score)::numeric, 1), 0) INTO v_avg_mood
|
|
||||||
FROM daily_logs
|
|
||||||
WHERE user_id = v_user_id
|
|
||||||
AND log_date >= p_start_date::date AND log_date <= p_end_date::date;
|
|
||||||
|
|
||||||
-- 5. Finance Trend (group by date)
|
|
||||||
SELECT COALESCE(jsonb_agg(
|
|
||||||
jsonb_build_object(
|
|
||||||
'date', t.t_date,
|
|
||||||
'income', t.inc,
|
|
||||||
'expense', t.exp
|
|
||||||
)
|
|
||||||
), '[]'::jsonb) INTO v_finance_trend
|
|
||||||
FROM (
|
|
||||||
SELECT
|
|
||||||
transaction_date AS t_date,
|
|
||||||
SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END) AS inc,
|
|
||||||
SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END) AS exp
|
|
||||||
FROM finance_transactions
|
|
||||||
WHERE user_id = v_user_id
|
|
||||||
AND transaction_date >= p_start_date::date AND transaction_date <= p_end_date::date
|
|
||||||
GROUP BY transaction_date
|
|
||||||
ORDER BY transaction_date ASC
|
|
||||||
) t;
|
|
||||||
|
|
||||||
-- 6. Mood Trend
|
|
||||||
SELECT COALESCE(jsonb_agg(
|
|
||||||
jsonb_build_object(
|
|
||||||
'date', log_date,
|
|
||||||
'mood', mood_score,
|
|
||||||
'energy', energy_score
|
|
||||||
)
|
|
||||||
), '[]'::jsonb) INTO v_mood_trend
|
|
||||||
FROM (
|
|
||||||
SELECT log_date, mood_score, energy_score
|
|
||||||
FROM daily_logs
|
|
||||||
WHERE user_id = v_user_id
|
|
||||||
AND log_date >= p_start_date::date AND log_date <= p_end_date::date
|
|
||||||
ORDER BY log_date ASC
|
|
||||||
) m;
|
|
||||||
|
|
||||||
RETURN jsonb_build_object(
|
|
||||||
'netProfit', v_net_profit,
|
|
||||||
'activeProjectsCount', v_active_projects,
|
|
||||||
'completedTasksCount', v_completed_tasks,
|
|
||||||
'avgMood', v_avg_mood::text,
|
|
||||||
'financeTrend', v_finance_trend,
|
|
||||||
'moodTrend', v_mood_trend
|
|
||||||
);
|
|
||||||
END;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION public.get_analytics_metrics(
|
|
||||||
p_start_date TIMESTAMP WITH TIME ZONE,
|
|
||||||
p_end_date TIMESTAMP WITH TIME ZONE
|
|
||||||
)
|
|
||||||
RETURNS JSONB
|
|
||||||
LANGUAGE plpgsql
|
|
||||||
SECURITY DEFINER
|
|
||||||
SET search_path = public
|
|
||||||
AS $$
|
|
||||||
DECLARE
|
|
||||||
v_user_id UUID := auth.uid();
|
|
||||||
v_project_income JSONB;
|
|
||||||
v_completed_tasks INT;
|
|
||||||
v_active_tasks INT;
|
|
||||||
BEGIN
|
|
||||||
IF v_user_id IS NULL THEN
|
|
||||||
RAISE EXCEPTION 'Not authenticated';
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- 1. Project Income Data
|
|
||||||
SELECT COALESCE(jsonb_agg(
|
|
||||||
jsonb_build_object(
|
|
||||||
'name', t.p_name,
|
|
||||||
'value', t.total_amount
|
|
||||||
)
|
|
||||||
), '[]'::jsonb) INTO v_project_income
|
|
||||||
FROM (
|
|
||||||
SELECT
|
|
||||||
COALESCE(p.name, 'Bilinmeyen') AS p_name,
|
|
||||||
SUM(f.amount) AS total_amount
|
|
||||||
FROM finance_transactions f
|
|
||||||
LEFT JOIN projects p ON f.project_id = p.id
|
|
||||||
WHERE f.user_id = v_user_id AND f.type = 'income'
|
|
||||||
AND f.transaction_date >= p_start_date::date AND f.transaction_date <= p_end_date::date
|
|
||||||
GROUP BY p.name
|
|
||||||
ORDER BY total_amount DESC
|
|
||||||
) t;
|
|
||||||
|
|
||||||
-- 2. Task completion stats
|
|
||||||
SELECT COUNT(*) INTO v_completed_tasks
|
|
||||||
FROM tasks
|
|
||||||
WHERE user_id = v_user_id AND status = 'done'
|
|
||||||
AND COALESCE(due_at, created_at) >= p_start_date AND COALESCE(due_at, created_at) <= p_end_date;
|
|
||||||
|
|
||||||
SELECT COUNT(*) INTO v_active_tasks
|
|
||||||
FROM tasks
|
|
||||||
WHERE user_id = v_user_id AND status != 'done' AND status != 'cancelled'
|
|
||||||
AND COALESCE(due_at, created_at) >= p_start_date AND COALESCE(due_at, created_at) <= p_end_date;
|
|
||||||
|
|
||||||
RETURN jsonb_build_object(
|
|
||||||
'projectIncomeData', v_project_income,
|
|
||||||
'completedTasks', v_completed_tasks,
|
|
||||||
'activeTasks', v_active_tasks
|
|
||||||
);
|
|
||||||
END;
|
|
||||||
$$;
|
|
||||||
@@ -1,341 +0,0 @@
|
|||||||
-- 0001: Initial Cognis baseline schema
|
|
||||||
-- This file is intentionally idempotent enough to be retried after a failed SQL Editor run.
|
|
||||||
|
|
||||||
-- Enable necessary extensions
|
|
||||||
create extension if not exists "uuid-ossp";
|
|
||||||
|
|
||||||
-- 1. Create journals table
|
|
||||||
create table if not exists public.journals (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
date timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
mood text not null,
|
|
||||||
energy smallint not null,
|
|
||||||
content text not null,
|
|
||||||
ai_tags text[] default array[]::text[],
|
|
||||||
ai_sentiment_score numeric,
|
|
||||||
ai_summary text,
|
|
||||||
ai_reflection text,
|
|
||||||
analysis_status text default 'pending'::text,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 2. Create tasks table
|
|
||||||
create table if not exists public.tasks (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
date timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
title text not null,
|
|
||||||
description text,
|
|
||||||
status text default 'todo'::text,
|
|
||||||
ai_generated boolean default false,
|
|
||||||
source_journal_id uuid references public.journals(id) on delete set null,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 3. Create chat_sessions table
|
|
||||||
create table if not exists public.chat_sessions (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
user_id uuid references auth.users(id) on delete cascade not null,
|
|
||||||
title text not null,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 4. Create chat_messages table
|
|
||||||
create table if not exists public.chat_messages (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
session_id uuid references public.chat_sessions(id) on delete cascade not null,
|
|
||||||
role text not null,
|
|
||||||
content text not null,
|
|
||||||
context_journal_ids uuid[] default array[]::uuid[],
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 5. Create profiles table
|
|
||||||
create table if not exists public.profiles (
|
|
||||||
id uuid references auth.users(id) on delete cascade primary key,
|
|
||||||
first_name text,
|
|
||||||
last_name text,
|
|
||||||
avatar_url text,
|
|
||||||
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- RLS (Row Level Security)
|
|
||||||
alter table public.journals enable row level security;
|
|
||||||
alter table public.tasks enable row level security;
|
|
||||||
alter table public.chat_sessions enable row level security;
|
|
||||||
alter table public.chat_messages enable row level security;
|
|
||||||
alter table public.profiles enable row level security;
|
|
||||||
|
|
||||||
-- Journals policies
|
|
||||||
drop policy if exists "Users can view their own journals." on public.journals;
|
|
||||||
create policy "Users can view their own journals." on public.journals
|
|
||||||
for select using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can insert their own journals." on public.journals;
|
|
||||||
create policy "Users can insert their own journals." on public.journals
|
|
||||||
for insert with check (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can update their own journals." on public.journals;
|
|
||||||
create policy "Users can update their own journals." on public.journals
|
|
||||||
for update using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own journals." on public.journals;
|
|
||||||
create policy "Users can delete their own journals." on public.journals
|
|
||||||
for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
-- Tasks policies
|
|
||||||
drop policy if exists "Users can view their own tasks." on public.tasks;
|
|
||||||
create policy "Users can view their own tasks." on public.tasks
|
|
||||||
for select using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can insert their own tasks." on public.tasks;
|
|
||||||
create policy "Users can insert their own tasks." on public.tasks
|
|
||||||
for insert with check (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can update their own tasks." on public.tasks;
|
|
||||||
create policy "Users can update their own tasks." on public.tasks
|
|
||||||
for update using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own tasks." on public.tasks;
|
|
||||||
create policy "Users can delete their own tasks." on public.tasks
|
|
||||||
for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
-- Chat sessions policies
|
|
||||||
drop policy if exists "Users can view their own chat sessions." on public.chat_sessions;
|
|
||||||
create policy "Users can view their own chat sessions." on public.chat_sessions
|
|
||||||
for select using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can insert their own chat sessions." on public.chat_sessions;
|
|
||||||
create policy "Users can insert their own chat sessions." on public.chat_sessions
|
|
||||||
for insert with check (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can update their own chat sessions." on public.chat_sessions;
|
|
||||||
create policy "Users can update their own chat sessions." on public.chat_sessions
|
|
||||||
for update using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own chat sessions." on public.chat_sessions;
|
|
||||||
create policy "Users can delete their own chat sessions." on public.chat_sessions
|
|
||||||
for delete using (auth.uid() = user_id);
|
|
||||||
|
|
||||||
-- Chat messages policies
|
|
||||||
drop policy if exists "Users can view their own chat messages." on public.chat_messages;
|
|
||||||
create policy "Users can view their own chat messages." on public.chat_messages
|
|
||||||
for select using (
|
|
||||||
exists (
|
|
||||||
select 1 from public.chat_sessions
|
|
||||||
where chat_sessions.id = chat_messages.session_id
|
|
||||||
and chat_sessions.user_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can insert their own chat messages." on public.chat_messages;
|
|
||||||
create policy "Users can insert their own chat messages." on public.chat_messages
|
|
||||||
for insert with check (
|
|
||||||
exists (
|
|
||||||
select 1 from public.chat_sessions
|
|
||||||
where chat_sessions.id = session_id
|
|
||||||
and chat_sessions.user_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can update their own chat messages." on public.chat_messages;
|
|
||||||
create policy "Users can update their own chat messages." on public.chat_messages
|
|
||||||
for update using (
|
|
||||||
exists (
|
|
||||||
select 1 from public.chat_sessions
|
|
||||||
where chat_sessions.id = session_id
|
|
||||||
and chat_sessions.user_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own chat messages." on public.chat_messages;
|
|
||||||
create policy "Users can delete their own chat messages." on public.chat_messages
|
|
||||||
for delete using (
|
|
||||||
exists (
|
|
||||||
select 1 from public.chat_sessions
|
|
||||||
where chat_sessions.id = session_id
|
|
||||||
and chat_sessions.user_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Profiles policies
|
|
||||||
drop policy if exists "Users can view their own profile." on public.profiles;
|
|
||||||
create policy "Users can view their own profile." on public.profiles
|
|
||||||
for select using (auth.uid() = id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can insert their own profile." on public.profiles;
|
|
||||||
create policy "Users can insert their own profile." on public.profiles
|
|
||||||
for insert with check (auth.uid() = id);
|
|
||||||
|
|
||||||
drop policy if exists "Users can update their own profile." on public.profiles;
|
|
||||||
create policy "Users can update their own profile." on public.profiles
|
|
||||||
for update using (auth.uid() = id);
|
|
||||||
|
|
||||||
-- First admin setup guard for self-hosted installations
|
|
||||||
create or replace function public.is_first_admin_setup_available()
|
|
||||||
returns boolean
|
|
||||||
language sql
|
|
||||||
security definer
|
|
||||||
set search_path = public
|
|
||||||
as $$
|
|
||||||
select not exists (
|
|
||||||
select 1
|
|
||||||
from public.profiles
|
|
||||||
limit 1
|
|
||||||
);
|
|
||||||
$$;
|
|
||||||
|
|
||||||
revoke all on function public.is_first_admin_setup_available() from public;
|
|
||||||
grant execute on function public.is_first_admin_setup_available() to anon;
|
|
||||||
grant execute on function public.is_first_admin_setup_available() to authenticated;
|
|
||||||
|
|
||||||
create or replace function public.neta_current_jwt_role()
|
|
||||||
returns text
|
|
||||||
language sql
|
|
||||||
stable
|
|
||||||
as $$
|
|
||||||
select coalesce(
|
|
||||||
nullif(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role', ''),
|
|
||||||
nullif(current_setting('request.jwt.claim.role', true), ''),
|
|
||||||
''
|
|
||||||
);
|
|
||||||
$$;
|
|
||||||
|
|
||||||
revoke all on function public.neta_current_jwt_role() from public;
|
|
||||||
grant execute on function public.neta_current_jwt_role() to anon;
|
|
||||||
grant execute on function public.neta_current_jwt_role() to authenticated;
|
|
||||||
grant execute on function public.neta_current_jwt_role() to service_role;
|
|
||||||
|
|
||||||
create schema if not exists neta_internal;
|
|
||||||
|
|
||||||
create table if not exists neta_internal.internal_auth_creations (
|
|
||||||
id uuid default uuid_generate_v4() primary key,
|
|
||||||
email text not null,
|
|
||||||
reason text default 'internal'::text not null,
|
|
||||||
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
|
||||||
expires_at timestamp with time zone default (timezone('utc'::text, now()) + interval '2 minutes') not null
|
|
||||||
);
|
|
||||||
|
|
||||||
create index if not exists internal_auth_creations_email_idx
|
|
||||||
on neta_internal.internal_auth_creations (lower(email));
|
|
||||||
|
|
||||||
revoke all on schema neta_internal from public;
|
|
||||||
revoke all on all tables in schema neta_internal from public;
|
|
||||||
|
|
||||||
create or replace function public.request_internal_auth_creation(
|
|
||||||
target_email text,
|
|
||||||
target_reason text default 'internal'
|
|
||||||
)
|
|
||||||
returns void
|
|
||||||
language plpgsql
|
|
||||||
security definer
|
|
||||||
set search_path = public, neta_internal
|
|
||||||
as $$
|
|
||||||
begin
|
|
||||||
if public.neta_current_jwt_role() <> 'service_role' then
|
|
||||||
raise exception 'Only service role can request internal auth creation.';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
if target_email is null or btrim(target_email) = '' then
|
|
||||||
raise exception 'target_email is required.';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
delete from neta_internal.internal_auth_creations
|
|
||||||
where expires_at <= timezone('utc'::text, now())
|
|
||||||
or lower(email) = lower(btrim(target_email));
|
|
||||||
|
|
||||||
insert into neta_internal.internal_auth_creations (email, reason)
|
|
||||||
values (btrim(target_email), coalesce(nullif(btrim(target_reason), ''), 'internal'));
|
|
||||||
end;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
revoke all on function public.request_internal_auth_creation(text, text) from public;
|
|
||||||
grant execute on function public.request_internal_auth_creation(text, text) to service_role;
|
|
||||||
|
|
||||||
-- Function to handle new user signup
|
|
||||||
create or replace function public.handle_new_user()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql
|
|
||||||
security definer
|
|
||||||
set search_path = public, neta_internal
|
|
||||||
as $$
|
|
||||||
declare
|
|
||||||
allowed_internal_creation boolean := false;
|
|
||||||
begin
|
|
||||||
delete from neta_internal.internal_auth_creations
|
|
||||||
where lower(email) = lower(new.email)
|
|
||||||
and expires_at > timezone('utc'::text, now())
|
|
||||||
returning true into allowed_internal_creation;
|
|
||||||
|
|
||||||
allowed_internal_creation := coalesce(allowed_internal_creation, false);
|
|
||||||
|
|
||||||
if exists (select 1 from public.profiles limit 1)
|
|
||||||
and coalesce(new.raw_app_meta_data->>'internal_created', 'false') <> 'true'
|
|
||||||
and not allowed_internal_creation then
|
|
||||||
raise exception 'Registration is closed. The first admin account already exists.';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
insert into public.profiles (id, first_name, last_name, avatar_url)
|
|
||||||
values (new.id, '', '', '')
|
|
||||||
on conflict (id) do nothing;
|
|
||||||
|
|
||||||
return new;
|
|
||||||
end;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
-- Trigger to automatically create profile on signup
|
|
||||||
drop trigger if exists on_auth_user_created on auth.users;
|
|
||||||
|
|
||||||
create trigger on_auth_user_created
|
|
||||||
after insert on auth.users
|
|
||||||
for each row execute procedure public.handle_new_user();
|
|
||||||
|
|
||||||
-- Setup storage bucket for avatars
|
|
||||||
insert into storage.buckets (id, name, public)
|
|
||||||
values ('avatars', 'avatars', true)
|
|
||||||
on conflict (id) do nothing;
|
|
||||||
|
|
||||||
drop policy if exists "Avatar images are publicly accessible." on storage.objects;
|
|
||||||
create policy "Avatar images are publicly accessible."
|
|
||||||
on storage.objects for select
|
|
||||||
using (
|
|
||||||
bucket_id = 'avatars'
|
|
||||||
or public.neta_current_jwt_role() = 'service_role'
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can upload an avatar." on storage.objects;
|
|
||||||
create policy "Users can upload an avatar."
|
|
||||||
on storage.objects for insert
|
|
||||||
with check (
|
|
||||||
bucket_id = 'avatars'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can update their own avatar." on storage.objects;
|
|
||||||
create policy "Users can update their own avatar."
|
|
||||||
on storage.objects for update
|
|
||||||
using (
|
|
||||||
bucket_id = 'avatars'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own avatar." on storage.objects;
|
|
||||||
create policy "Users can delete their own avatar."
|
|
||||||
on storage.objects for delete
|
|
||||||
using (
|
|
||||||
bucket_id = 'avatars'
|
|
||||||
and (
|
|
||||||
public.neta_current_jwt_role() = 'service_role'
|
|
||||||
or auth.uid()::text = (storage.foldername(name))[1]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
-- Seed Data for Local Development
|
|
||||||
-- This file populates the database with demo data when running `supabase start` or `supabase db reset`.
|
|
||||||
|
|
||||||
-- 1. Create a dummy user
|
|
||||||
-- Password for this user: "demo123456"
|
|
||||||
-- Wait! Direct insert into auth.users is complex due to password hashing.
|
|
||||||
-- For local development, it's easier to sign up via the UI first.
|
|
||||||
-- However, we can create a function that populates demo data for a specific user ID, or we can insert an auth user with a pre-calculated crypt hash.
|
|
||||||
-- Using a standard bcrypt hash for 'demo123456': $2a$10$wT0/487K12z0fB4n0p.PMeI5b1aH03T1gB8Kj1k1k1k1k1k1k1k1k
|
|
||||||
|
|
||||||
INSERT INTO auth.users (id, instance_id, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data, created_at, updated_at, role, confirmation_token, email_change, email_change_token_new, recovery_token)
|
|
||||||
VALUES (
|
|
||||||
'00000000-0000-0000-0000-000000000000',
|
|
||||||
'00000000-0000-0000-0000-000000000000',
|
|
||||||
'demo@cognis.com',
|
|
||||||
crypt('demo123456', gen_salt('bf')),
|
|
||||||
now(),
|
|
||||||
'{"provider": "email", "providers": ["email"]}',
|
|
||||||
'{}',
|
|
||||||
now(),
|
|
||||||
now(),
|
|
||||||
'authenticated',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
''
|
|
||||||
) ON CONFLICT (id) DO NOTHING;
|
|
||||||
|
|
||||||
INSERT INTO auth.identities (id, user_id, provider_id, identity_data, provider, last_sign_in_at, created_at, updated_at)
|
|
||||||
VALUES (
|
|
||||||
'00000000-0000-0000-0000-000000000000',
|
|
||||||
'00000000-0000-0000-0000-000000000000',
|
|
||||||
'00000000-0000-0000-0000-000000000000',
|
|
||||||
format('{"sub": "%s", "email": "demo@cognis.com"}', '00000000-0000-0000-0000-000000000000')::jsonb,
|
|
||||||
'email',
|
|
||||||
now(),
|
|
||||||
now(),
|
|
||||||
now()
|
|
||||||
) ON CONFLICT (id) DO NOTHING;
|
|
||||||
|
|
||||||
-- Profile for Demo User
|
|
||||||
INSERT INTO public.profiles (id, first_name, last_name, avatar_url)
|
|
||||||
VALUES (
|
|
||||||
'00000000-0000-0000-0000-000000000000',
|
|
||||||
'Demo',
|
|
||||||
'User',
|
|
||||||
''
|
|
||||||
) ON CONFLICT (id) DO NOTHING;
|
|
||||||
|
|
||||||
-- App Settings for Demo User
|
|
||||||
INSERT INTO public.app_settings (user_id, ai_provider, api_key)
|
|
||||||
VALUES (
|
|
||||||
'00000000-0000-0000-0000-000000000000',
|
|
||||||
'ollama',
|
|
||||||
''
|
|
||||||
) ON CONFLICT (user_id) DO NOTHING;
|
|
||||||
|
|
||||||
-- Seed Projects
|
|
||||||
INSERT INTO public.projects (id, user_id, name, type, status, description, progress)
|
|
||||||
VALUES
|
|
||||||
('11111111-1111-1111-1111-111111111111', '00000000-0000-0000-0000-000000000000', 'E-Ticaret Yeniden Tasarımı', 'client_project', 'active', 'Ana sayfa ve ödeme adımlarının yenilenmesi.', 40),
|
|
||||||
('22222222-2222-2222-2222-222222222222', '00000000-0000-0000-0000-000000000000', 'Cognis MVP Geliştirme', 'side_project', 'active', 'Freelancer işletim sistemi geliştirme süreci.', 85)
|
|
||||||
ON CONFLICT (id) DO NOTHING;
|
|
||||||
|
|
||||||
-- Seed Tasks
|
|
||||||
INSERT INTO public.tasks (id, user_id, title, status, project_id, created_at, due_at)
|
|
||||||
VALUES
|
|
||||||
(uuid_generate_v4(), '00000000-0000-0000-0000-000000000000', 'Ana Sayfa Tasarımı (Wireframe)', 'completed', '11111111-1111-1111-1111-111111111111', now() - interval '5 days', now() - interval '2 days'),
|
|
||||||
(uuid_generate_v4(), '00000000-0000-0000-0000-000000000000', 'Ödeme Adımı Entegrasyonu', 'in_progress', '11111111-1111-1111-1111-111111111111', now() - interval '2 days', now() + interval '3 days'),
|
|
||||||
(uuid_generate_v4(), '00000000-0000-0000-0000-000000000000', 'Dashboard UI Güncellemesi', 'todo', '22222222-2222-2222-2222-222222222222', now() - interval '1 day', now() + interval '5 days')
|
|
||||||
ON CONFLICT DO NOTHING;
|
|
||||||
|
|
||||||
-- Seed Finances
|
|
||||||
INSERT INTO public.finance_transactions (id, user_id, type, amount, category, project_id, transaction_date)
|
|
||||||
VALUES
|
|
||||||
(uuid_generate_v4(), '00000000-0000-0000-0000-000000000000', 'income', 15000.00, 'Proje Avansı', '11111111-1111-1111-1111-111111111111', now() - interval '10 days'),
|
|
||||||
(uuid_generate_v4(), '00000000-0000-0000-0000-000000000000', 'expense', 450.00, 'Yazılım Abonelikleri', NULL, now() - interval '5 days'),
|
|
||||||
(uuid_generate_v4(), '00000000-0000-0000-0000-000000000000', 'expense', 120.00, 'Kahve / Yemek', NULL, now() - interval '1 day')
|
|
||||||
ON CONFLICT DO NOTHING;
|
|
||||||
|
|
||||||
-- Seed Daily Logs
|
|
||||||
INSERT INTO public.daily_logs (id, user_id, log_date, mood_score, energy_score, notes)
|
|
||||||
VALUES
|
|
||||||
(uuid_generate_v4(), '00000000-0000-0000-0000-000000000000', current_date - interval '2 days', 4, 3, 'Biraz yorgun ama verimli geçti.'),
|
|
||||||
(uuid_generate_v4(), '00000000-0000-0000-0000-000000000000', current_date - interval '1 day', 5, 5, 'Harika odaklandım, projenin %80 ini bitirdim.'),
|
|
||||||
(uuid_generate_v4(), '00000000-0000-0000-0000-000000000000', current_date, 3, 2, 'Biraz uykusuzum.')
|
|
||||||
ON CONFLICT DO NOTHING;
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
-- Seed 0001: Demo Freelancer OS data
|
|
||||||
-- Run after: supabase/migrations/0002_add_freelancer_os_core_tables.sql
|
|
||||||
--
|
|
||||||
-- Before running, replace the UUID below with an existing auth.users.id.
|
|
||||||
-- This file is for local/demo environments. Do not run it on production data.
|
|
||||||
|
|
||||||
with seed_user as (
|
|
||||||
select '00000000-0000-0000-0000-000000000000'::uuid as user_id
|
|
||||||
),
|
|
||||||
demo_clients as (
|
|
||||||
insert into public.clients (user_id, name, company_name, email, status, notes)
|
|
||||||
select user_id, 'Acme Studio', 'Acme Studio LLC', 'hello@acme.example', 'active', 'Primary design and web retainer client.'
|
|
||||||
from seed_user
|
|
||||||
union all
|
|
||||||
select user_id, 'Northwind Labs', 'Northwind Labs', 'ops@northwind.example', 'active', 'Product strategy project with monthly milestones.'
|
|
||||||
from seed_user
|
|
||||||
returning id, user_id, name
|
|
||||||
),
|
|
||||||
demo_projects as (
|
|
||||||
insert into public.projects (user_id, client_id, name, type, description, status, start_date, due_date, budget_amount, currency, progress)
|
|
||||||
select
|
|
||||||
c.user_id,
|
|
||||||
c.id,
|
|
||||||
'Acme Website Refresh',
|
|
||||||
'client_project',
|
|
||||||
'Marketing website refresh and conversion improvements.',
|
|
||||||
'active',
|
|
||||||
current_date - 14,
|
|
||||||
current_date + 21,
|
|
||||||
4200,
|
|
||||||
'USD',
|
|
||||||
45
|
|
||||||
from demo_clients c
|
|
||||||
where c.name = 'Acme Studio'
|
|
||||||
union all
|
|
||||||
select
|
|
||||||
c.user_id,
|
|
||||||
c.id,
|
|
||||||
'Northwind Product Audit',
|
|
||||||
'client_project',
|
|
||||||
'Audit onboarding, analytics, and project roadmap.',
|
|
||||||
'active',
|
|
||||||
current_date - 7,
|
|
||||||
current_date + 14,
|
|
||||||
2800,
|
|
||||||
'USD',
|
|
||||||
30
|
|
||||||
from demo_clients c
|
|
||||||
where c.name = 'Northwind Labs'
|
|
||||||
union all
|
|
||||||
select
|
|
||||||
user_id,
|
|
||||||
null,
|
|
||||||
'Cognis Open Source Launch',
|
|
||||||
'side_project',
|
|
||||||
'Prepare the self-hosted Freelancer OS MVP for public release.',
|
|
||||||
'planning',
|
|
||||||
current_date,
|
|
||||||
current_date + 45,
|
|
||||||
null,
|
|
||||||
'USD',
|
|
||||||
10
|
|
||||||
from seed_user
|
|
||||||
returning id, user_id, client_id, name
|
|
||||||
),
|
|
||||||
demo_tasks as (
|
|
||||||
insert into public.tasks (user_id, client_id, project_id, title, description, status, priority, due_at, estimated_minutes, actual_minutes)
|
|
||||||
select
|
|
||||||
p.user_id,
|
|
||||||
p.client_id,
|
|
||||||
p.id,
|
|
||||||
'Prepare homepage wireframe',
|
|
||||||
'Create first-pass homepage structure for client review.',
|
|
||||||
'todo',
|
|
||||||
'high',
|
|
||||||
timezone('utc'::text, now()) + interval '2 days',
|
|
||||||
180,
|
|
||||||
null
|
|
||||||
from demo_projects p
|
|
||||||
where p.name = 'Acme Website Refresh'
|
|
||||||
union all
|
|
||||||
select
|
|
||||||
p.user_id,
|
|
||||||
p.client_id,
|
|
||||||
p.id,
|
|
||||||
'Review onboarding analytics',
|
|
||||||
'Summarize funnel drop-off points and quick wins.',
|
|
||||||
'in_progress',
|
|
||||||
'urgent',
|
|
||||||
timezone('utc'::text, now()) + interval '1 day',
|
|
||||||
120,
|
|
||||||
45
|
|
||||||
from demo_projects p
|
|
||||||
where p.name = 'Northwind Product Audit'
|
|
||||||
union all
|
|
||||||
select
|
|
||||||
p.user_id,
|
|
||||||
p.client_id,
|
|
||||||
p.id,
|
|
||||||
'Write MVP installation notes',
|
|
||||||
'Document self-host setup, env vars, and migration order.',
|
|
||||||
'todo',
|
|
||||||
'medium',
|
|
||||||
timezone('utc'::text, now()) + interval '5 days',
|
|
||||||
90,
|
|
||||||
null
|
|
||||||
from demo_projects p
|
|
||||||
where p.name = 'Cognis Open Source Launch'
|
|
||||||
returning id, user_id, client_id, project_id, title
|
|
||||||
),
|
|
||||||
demo_events as (
|
|
||||||
insert into public.calendar_events (user_id, client_id, project_id, task_id, title, description, type, starts_at, ends_at)
|
|
||||||
select
|
|
||||||
t.user_id,
|
|
||||||
t.client_id,
|
|
||||||
t.project_id,
|
|
||||||
t.id,
|
|
||||||
'Client review: homepage wireframe',
|
|
||||||
'Review initial direction with Acme.',
|
|
||||||
'meeting',
|
|
||||||
timezone('utc'::text, now()) + interval '3 days',
|
|
||||||
timezone('utc'::text, now()) + interval '3 days 1 hour'
|
|
||||||
from demo_tasks t
|
|
||||||
where t.title = 'Prepare homepage wireframe'
|
|
||||||
union all
|
|
||||||
select
|
|
||||||
t.user_id,
|
|
||||||
t.client_id,
|
|
||||||
t.project_id,
|
|
||||||
t.id,
|
|
||||||
'Deep work: analytics audit',
|
|
||||||
'Focus block for Northwind analysis.',
|
|
||||||
'focus',
|
|
||||||
timezone('utc'::text, now()) + interval '1 day',
|
|
||||||
timezone('utc'::text, now()) + interval '1 day 2 hours'
|
|
||||||
from demo_tasks t
|
|
||||||
where t.title = 'Review onboarding analytics'
|
|
||||||
returning id
|
|
||||||
),
|
|
||||||
demo_finance as (
|
|
||||||
insert into public.finance_transactions (user_id, client_id, project_id, type, amount, currency, transaction_date, category, payment_status, description)
|
|
||||||
select
|
|
||||||
p.user_id,
|
|
||||||
p.client_id,
|
|
||||||
p.id,
|
|
||||||
'income',
|
|
||||||
2100,
|
|
||||||
'USD',
|
|
||||||
current_date - 3,
|
|
||||||
'Project deposit',
|
|
||||||
'paid',
|
|
||||||
'Acme Website Refresh initial payment.'
|
|
||||||
from demo_projects p
|
|
||||||
where p.name = 'Acme Website Refresh'
|
|
||||||
union all
|
|
||||||
select
|
|
||||||
p.user_id,
|
|
||||||
p.client_id,
|
|
||||||
p.id,
|
|
||||||
'income',
|
|
||||||
2800,
|
|
||||||
'USD',
|
|
||||||
current_date + 10,
|
|
||||||
'Project payment',
|
|
||||||
'pending',
|
|
||||||
'Northwind audit payment due.'
|
|
||||||
from demo_projects p
|
|
||||||
where p.name = 'Northwind Product Audit'
|
|
||||||
union all
|
|
||||||
select
|
|
||||||
user_id,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
'expense',
|
|
||||||
49,
|
|
||||||
'USD',
|
|
||||||
current_date,
|
|
||||||
'Software',
|
|
||||||
'paid',
|
|
||||||
'Monthly tool subscription.'
|
|
||||||
from seed_user
|
|
||||||
returning id
|
|
||||||
),
|
|
||||||
demo_daily_logs as (
|
|
||||||
insert into public.daily_logs (user_id, log_date, mood_score, energy_score, work_satisfaction_score, note)
|
|
||||||
select user_id, current_date - 2, 3, 3, 3, 'Mixed admin day with some client context switching.'
|
|
||||||
from seed_user
|
|
||||||
union all
|
|
||||||
select user_id, current_date - 1, 4, 5, 4, 'Strong focus block and clear project progress.'
|
|
||||||
from seed_user
|
|
||||||
union all
|
|
||||||
select user_id, current_date, 4, 4, 4, 'Good planning momentum for the week.'
|
|
||||||
from seed_user
|
|
||||||
on conflict (user_id, log_date) do update set
|
|
||||||
mood_score = excluded.mood_score,
|
|
||||||
energy_score = excluded.energy_score,
|
|
||||||
work_satisfaction_score = excluded.work_satisfaction_score,
|
|
||||||
note = excluded.note,
|
|
||||||
updated_at = timezone('utc'::text, now())
|
|
||||||
returning id
|
|
||||||
)
|
|
||||||
insert into public.app_settings (user_id, timezone, currency, ai_provider, ai_model)
|
|
||||||
select user_id, 'UTC', 'USD', 'ollama', 'llama3'
|
|
||||||
from seed_user
|
|
||||||
on conflict (user_id) do update set
|
|
||||||
timezone = excluded.timezone,
|
|
||||||
currency = excluded.currency,
|
|
||||||
ai_provider = excluded.ai_provider,
|
|
||||||
ai_model = excluded.ai_model,
|
|
||||||
updated_at = timezone('utc'::text, now());
|
|
||||||
-1731
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user