diff --git a/.env.example b/.env.example index f2bf2e9..4ec48da 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,23 @@ -# ─── Supabase (Zorunlu) ─────────────────────────────────────── -NEXT_PUBLIC_SUPABASE_URL=your_supabase_url -NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key +# Neta self-host environment -# Müşteri Portalı hesabı oluşturmak için gerekli (Freelancer panelinde kullanılır) -SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key +# Public URL where users open Neta. +NEXT_PUBLIC_SITE_URL=http://localhost:3000 -# ─── AI Sağlayıcılar (Opsiyonel — uygulama içi ayarlardan da tanımlanabilir) ─── -# OPENAI_API_KEY=your_openai_api_key -# GROQ_API_KEY=your_groq_api_key -# GEMINI_API_KEY=your_gemini_api_key +# Host port mapped to the Neta container's internal port 3000. +NETA_PORT=3000 -# ─── Supabase Auth (Self-host'ta gerekli olabilir) ─────────── -# NEXT_PUBLIC_SITE_URL=https://neta.example.com +# Supabase API URL. For a hosted project this looks like: +# https://your-project.supabase.co +# For a bundled/self-hosted Supabase setup this is usually your public API URL. +NEXT_PUBLIC_SUPABASE_URL= + +# Supabase anon/public API key. +NEXT_PUBLIC_SUPABASE_ANON_KEY= + +# Supabase service role key. Required for creating client portal accounts. +# Keep this secret. It is only used server-side. +SUPABASE_SERVICE_ROLE_KEY= + +# Optional: direct Postgres connection string used only by bash ./scripts/apply-migrations.sh. +# Do not expose this to browsers. It is not required by the web container. +# DATABASE_URL=postgresql://postgres:password@host:5432/postgres diff --git a/README.md b/README.md index 6f14966..a32ee1a 100644 --- a/README.md +++ b/README.md @@ -50,32 +50,45 @@ Neta is engineered using modern, high-performance web technologies: ## Installation and Deployment -Neta is designed to be easily self-hosted. Follow these steps to deploy the application on your own infrastructure. +Neta is designed for self-hosting. The current Docker Compose file runs the Neta web application and connects it to a Supabase-compatible backend. A bundled Supabase Compose profile is planned for the full-stack self-host mode. ### Prerequisites - Docker and Docker Compose -- Node.js (for local development) -- A Supabase instance (Cloud or Self-Hosted) +- A Supabase project or self-hosted Supabase backend +- Supabase API URL, anon key, and service role key +- A direct Postgres `DATABASE_URL` if you want the installer to apply migrations automatically ### 1-Click Installation (Recommended) -You can install and start Neta immediately using our automated setup script. Simply run the following command in your terminal: +You can install Neta using the interactive setup script: ```bash curl -sL https://raw.githubusercontent.com/poyrazavsever/neta/main/install.sh | bash ``` +The installer asks for the required Supabase values, writes a `.env` file, optionally applies database migrations, validates Docker Compose configuration, and starts the application. + ### Manual Installation If you prefer to set up Neta manually: 1. Clone the repository: `git clone https://github.com/poyrazavsever/neta.git` -2. Navigate to the directory and copy the `.env.example` file to `.env.local`. -3. Build and start the Docker container: +2. Navigate to the directory and copy the `.env.example` file to `.env`. +3. Fill every required value in `.env`. +4. Apply database migrations: + ```bash -docker-compose up -d --build +DATABASE_URL='postgresql://postgres:password@host:5432/postgres' bash ./scripts/apply-migrations.sh ``` +5. Build and start the Docker container: + +```bash +docker compose up -d --build +``` + +Docker Compose intentionally fails fast when required Supabase environment values are missing. + ### First Administrator Account To ensure data security, Neta is locked to a single administrator. Upon launching the application for the first time, navigate to the `/register` route to create the initial admin account. Once this account is created, public registration is permanently disabled. diff --git a/docker-compose.yml b/docker-compose.yml index 70b7b1c..1a7385a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,18 +4,16 @@ services: context: . dockerfile: Dockerfile args: - NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL} - NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY} + NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL:?Set NEXT_PUBLIC_SUPABASE_URL in .env} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY:?Set NEXT_PUBLIC_SUPABASE_ANON_KEY in .env} NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost:3000} container_name: neta-web restart: unless-stopped ports: - - "3000:3000" + - "${NETA_PORT:-3000}:3000" environment: - - NODE_ENV=production - - NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL} - - NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY} - - SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY} - - NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL:-http://localhost:3000} - env_file: - - .env.local + NODE_ENV: production + NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL:?Set NEXT_PUBLIC_SUPABASE_URL in .env} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY:?Set NEXT_PUBLIC_SUPABASE_ANON_KEY in .env} + SUPABASE_SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY:?Set SUPABASE_SERVICE_ROLE_KEY in .env} + NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost:3000} diff --git a/docs/database/0004-business-os-tables.md b/docs/database/0004-business-os-tables.md new file mode 100644 index 0000000..359bebe --- /dev/null +++ b/docs/database/0004-business-os-tables.md @@ -0,0 +1,22 @@ +# 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`. diff --git a/docs/database/0005-advanced-crm-tables.md b/docs/database/0005-advanced-crm-tables.md new file mode 100644 index 0000000..db5d273 --- /dev/null +++ b/docs/database/0005-advanced-crm-tables.md @@ -0,0 +1,28 @@ +# 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`. diff --git a/docs/database/0006-pgvector-and-embeddings.md b/docs/database/0006-pgvector-and-embeddings.md new file mode 100644 index 0000000..28cad87 --- /dev/null +++ b/docs/database/0006-pgvector-and-embeddings.md @@ -0,0 +1,25 @@ +# 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`. diff --git a/docs/database/0007-client-portal-tables.md b/docs/database/0007-client-portal-tables.md new file mode 100644 index 0000000..8764656 --- /dev/null +++ b/docs/database/0007-client-portal-tables.md @@ -0,0 +1,25 @@ +# 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`. diff --git a/docs/database/0008-project-progress-and-quota.md b/docs/database/0008-project-progress-and-quota.md new file mode 100644 index 0000000..84baee0 --- /dev/null +++ b/docs/database/0008-project-progress-and-quota.md @@ -0,0 +1,29 @@ +# 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`. diff --git a/docs/database/query-order.md b/docs/database/query-order.md index 5023397..f69c6af 100644 --- a/docs/database/query-order.md +++ b/docs/database/query-order.md @@ -7,6 +7,11 @@ This file is the canonical order of SQL files for database setup and migration. | 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 | | seed-0001 | `supabase/seeds/0001_demo_freelancer_os_data.sql` | `docs/database/seed-0001-demo-freelancer-os-data.md` | Optional demo seed, pending execution | @@ -23,3 +28,13 @@ This file is the canonical order of SQL files for database setup and migration. 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 + +Use the migration helper from the repository root: + +```bash +DATABASE_URL='postgresql://postgres:password@host:5432/postgres' bash ./scripts/apply-migrations.sh +``` + +The helper applies `0001` through `0009` in the order listed above. It uses local `psql` when available, otherwise it runs `psql` through Docker. diff --git a/install.sh b/install.sh index 6701365..fb7e38f 100644 --- a/install.sh +++ b/install.sh @@ -1,57 +1,150 @@ -#!/bin/bash +#!/usr/bin/env bash -# Neta - Freelancer Operating System Installation Script +# Neta - Freelancer Operating System installer -set -e +set -euo pipefail -echo "🚀 Neta kurulumu başlıyor..." +REPO_URL="${NETA_REPO_URL:-https://github.com/poyrazavsever/neta.git}" +TARGET_DIR="${NETA_TARGET_DIR:-neta-os}" -# 1. Check for Git -if ! [ -x "$(command -v git)" ]; then - echo "Hata: Git yüklü değil. Lütfen önce Git'i kurun." >&2 +info() { + printf "\n%s\n" "$1" +} + +fail() { + echo "Error: $1" >&2 exit 1 -fi +} -# 2. Check for Docker -if ! [ -x "$(command -v docker)" ]; then - echo "Hata: Docker yüklü değil. Lütfen önce Docker'ı kurun." >&2 - exit 1 -fi +require_command() { + command -v "$1" >/dev/null 2>&1 || fail "$1 is required." +} -# 3. Target directory -TARGET_DIR="neta-os" +compose_cmd() { + if docker compose version >/dev/null 2>&1; then + echo "docker compose" + elif command -v docker-compose >/dev/null 2>&1; then + echo "docker-compose" + else + fail "Docker Compose is required." + fi +} -if [ -d "$TARGET_DIR" ]; then - echo "Hata: '$TARGET_DIR' dizini zaten var. Lütfen farklı bir dizinde deneyin veya mevcut dizini silin." >&2 - exit 1 -fi +prompt_required() { + local var_name="$1" + local label="$2" + local current_value="${!var_name:-}" + local value -# 4. Clone repository -echo "📦 GitHub'dan kodlar indiriliyor..." -git clone https://github.com/poyrazavsever/neta.git "$TARGET_DIR" -cd "$TARGET_DIR" + if [ -n "$current_value" ]; then + return + fi -# 5. Setup environment variables -echo "⚙️ Çevresel değişkenler ayarlanıyor..." -if [ -f ".env.example" ]; then - cp .env.example .env.local -else - echo "Uyarı: .env.example dosyası bulunamadı." -fi + while true; do + read -r -p "$label: " value + if [ -n "$value" ]; then + printf -v "$var_name" "%s" "$value" + export "$var_name" + return + fi + echo "This value is required." + done +} -# 6. Start with Docker Compose -echo "🐳 Docker container'ları inşa ediliyor ve başlatılıyor..." -if docker compose version > /dev/null 2>&1; then - docker compose up -d --build -elif docker-compose version > /dev/null 2>&1; then - docker-compose up -d --build -else - echo "Hata: docker-compose komutu bulunamadı." >&2 - exit 1 -fi +prompt_optional() { + local var_name="$1" + local label="$2" + local default_value="$3" + local current_value="${!var_name:-}" + local value -echo "" -echo "✅ Kurulum tamamlandı!" -echo "Neta başarıyla ayağa kaldırıldı." -echo "Tarayıcınızdan http://localhost:3000 adresine giderek Neta'yı kullanmaya başlayabilirsiniz." -echo "İlk girişte oluşturacağınız hesap, sistemin tek yöneticisi (admin) olacaktır." + if [ -n "$current_value" ]; then + return + fi + + read -r -p "$label [$default_value]: " value + printf -v "$var_name" "%s" "${value:-$default_value}" + export "$var_name" +} + +prompt_secret_required() { + local var_name="$1" + local label="$2" + local current_value="${!var_name:-}" + local value + + if [ -n "$current_value" ]; then + return + fi + + while true; do + read -r -s -p "$label: " value + echo + if [ -n "$value" ]; then + printf -v "$var_name" "%s" "$value" + export "$var_name" + return + fi + echo "This value is required." + done +} + +write_env_file() { + cat > .env </dev/null 2>&1 || fail "Docker is installed but the daemon is not reachable." + + if [ -d "$TARGET_DIR" ]; then + fail "'$TARGET_DIR' already exists. Set NETA_TARGET_DIR or remove the directory." + fi + + info "Cloning Neta into $TARGET_DIR" + git clone "$REPO_URL" "$TARGET_DIR" + cd "$TARGET_DIR" + + prompt_optional NEXT_PUBLIC_SITE_URL "Public Neta URL" "http://localhost:3000" + prompt_optional NETA_PORT "Host port for Neta" "3000" + prompt_required NEXT_PUBLIC_SUPABASE_URL "Supabase API URL" + prompt_secret_required NEXT_PUBLIC_SUPABASE_ANON_KEY "Supabase anon key" + prompt_secret_required SUPABASE_SERVICE_ROLE_KEY "Supabase service role key" + + write_env_file + info "Wrote .env" + + read -r -p "Apply Neta database migrations now? Requires a direct Postgres DATABASE_URL. [y/N]: " apply_migrations + if [ "$apply_migrations" = "y" ] || [ "$apply_migrations" = "Y" ]; then + prompt_secret_required DATABASE_URL "Postgres DATABASE_URL" + DATABASE_URL="$DATABASE_URL" bash ./scripts/apply-migrations.sh + else + echo "Skipping migrations. Run them later with:" + echo " DATABASE_URL='postgresql://...' bash ./scripts/apply-migrations.sh" + fi + + local compose + compose="$(compose_cmd)" + + info "Validating Docker Compose configuration" + $compose config >/dev/null + + info "Building and starting Neta" + $compose up -d --build + + info "Neta is starting" + echo "Open: $NEXT_PUBLIC_SITE_URL" + echo "Create the first admin account at: $NEXT_PUBLIC_SITE_URL/register" +} + +main "$@" diff --git a/scripts/apply-migrations.sh b/scripts/apply-migrations.sh new file mode 100644 index 0000000..78d479f --- /dev/null +++ b/scripts/apply-migrations.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [ -z "${DATABASE_URL:-}" ]; then + echo "DATABASE_URL is required." >&2 + echo "Example:" >&2 + echo " DATABASE_URL='postgresql://postgres:password@host:5432/postgres' bash ./scripts/apply-migrations.sh" >&2 + exit 1 +fi + +SQL_FILES=( + "supabase/schema.sql" + "supabase/migrations/0002_add_freelancer_os_core_tables.sql" + "supabase/migrations/0003_add_project_planning_assets.sql" + "supabase/migrations/0004_add_business_os_tables.sql" + "supabase/migrations/0005_add_advanced_crm_tables.sql" + "supabase/migrations/0006_add_pgvector_and_embeddings.sql" + "supabase/migrations/0007_add_client_portal_tables.sql" + "supabase/migrations/0008_add_project_progress_and_quota.sql" + "supabase/migrations/0009_lock_registration_after_first_admin.sql" +) + +run_sql_file() { + local file_path="$1" + local absolute_path="$ROOT_DIR/$file_path" + + if [ ! -f "$absolute_path" ]; then + echo "Missing SQL file: $file_path" >&2 + exit 1 + fi + + echo "Applying $file_path" + + if command -v psql >/dev/null 2>&1; then + psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f "$absolute_path" + elif command -v docker >/dev/null 2>&1; then + docker run --rm -i postgres:16-alpine \ + psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f - < "$absolute_path" + else + echo "Neither psql nor docker is available to run migrations." >&2 + exit 1 + fi +} + +for sql_file in "${SQL_FILES[@]}"; do + run_sql_file "$sql_file" +done + +echo "All Neta migrations were applied." diff --git a/supabase/README.md b/supabase/README.md index f70aa62..4ce5387 100644 --- a/supabase/README.md +++ b/supabase/README.md @@ -29,4 +29,21 @@ Do not overwrite already executed SQL without also creating a new ordered migrat 1. `schema.sql` 2. `migrations/0002_add_freelancer_os_core_tables.sql` -3. Optional: `seeds/0001_demo_freelancer_os_data.sql` +3. `migrations/0003_add_project_planning_assets.sql` +4. `migrations/0004_add_business_os_tables.sql` +5. `migrations/0005_add_advanced_crm_tables.sql` +6. `migrations/0006_add_pgvector_and_embeddings.sql` +7. `migrations/0007_add_client_portal_tables.sql` +8. `migrations/0008_add_project_progress_and_quota.sql` +9. `migrations/0009_lock_registration_after_first_admin.sql` +10. Optional local/demo data: `seeds/0001_demo_freelancer_os_data.sql` + +## Apply Migrations + +From the repository root: + +```bash +DATABASE_URL='postgresql://postgres:password@host:5432/postgres' bash ./scripts/apply-migrations.sh +``` + +The script applies all required schema files in canonical order. It requires either local `psql` or Docker. If `psql` is not installed, it runs `psql` through the `postgres:16-alpine` Docker image.