diff --git a/README.md b/README.md
index 0f76589..17d51cf 100644
--- a/README.md
+++ b/README.md
@@ -61,43 +61,22 @@ Visitran is an **open-source** data transformation tool that supports **no-code*
## Getting Started
-Choose your preferred installation method:
-
-- [Docker Compose](#option-1-docker-compose) — Recommended for quick evaluation
-- [Direct Installation (localhost)](#option-2-direct-installation-localhost) — For development and customization
-
-### Option 1: Docker Compose
-
-The fastest way to get Visitran running. Requires [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/).
+Requires [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/).
```bash
-# Clone the repository
git clone https://github.com/Zipstack/visitran.git
cd visitran
-
-# Set up backend environment variables
cp backend/sample.env backend/.env
-```
-
-**Edit `backend/.env`** — default values are provided for quick start. For production, replace these keys:
-
-| Variable | Default | How to Generate (production) |
-|----------|---------|------------------------------|
-| `SECRET_KEY` | Provided | Use [djecrety.ir](https://djecrety.ir/) or run: `python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"` |
-| `VISITRAN_ENCRYPTION_KEY` | Provided | Run: `python -c "from base64 import b64encode; from cryptography.fernet import Fernet; print(b64encode(Fernet.generate_key()).decode())"` |
-| `VISITRAN_RSA_PRIVATE_KEY` | Provided | Generate a 2048-bit RSA key pair (see `sample.env` for the command) |
-| `VISITRAN_RSA_PUBLIC_KEY` | Provided | Derived from the private key above |
-| `VISITRAN_AI_KEY` | Empty | Optional — get from [app.visitran.com](https://app.visitran.com) to enable AI features |
-
-> **Note:** The sample.env is pre-configured for Docker — `DB_HOST=postgres` and `REDIS_HOST=redis` point to the Docker Compose service names. No hostname changes needed. You can run `docker compose up --build -d` immediately after copying.
-
-```bash
-# Build and start all services
cd docker
docker compose up --build -d
```
-This starts:
+Open [http://localhost:3000](http://localhost:3000) and **Sign Up** to create your account — that's it!
+
+> **AI Features (optional):** Get an API key from [app.visitran.com](https://app.visitran.com) and add `VISITRAN_AI_KEY=vtk_...` to `backend/.env`, then restart with `docker compose restart backend`.
+
+
+Services started by Docker Compose
| Service | Port | Description |
|---------|------|-------------|
@@ -108,23 +87,27 @@ This starts:
| Celery Worker | — | Background job processing |
| Celery Beat | — | Scheduled task processing |
-Open `http://localhost:3000` in your browser.
+To stop: `docker compose down`
+To stop and delete all data: `docker compose down -v`
-**First-time setup:** On first launch, click **Sign Up** to create a local admin account. There is no default username/password — you set your own credentials during signup.
+
-To stop:
+
+Environment variables
-```bash
-docker compose down
-```
+The `sample.env` ships with working defaults — no edits needed to get started. For production, replace these keys:
-To stop and **delete all data** (PostgreSQL volume):
+| Variable | Default | How to Generate (production) |
+|----------|---------|------------------------------|
+| `SECRET_KEY` | Provided | Use [djecrety.ir](https://djecrety.ir/) or `python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"` |
+| `VISITRAN_ENCRYPTION_KEY` | Provided | `python -c "from base64 import b64encode; from cryptography.fernet import Fernet; print(b64encode(Fernet.generate_key()).decode())"` |
+| `VISITRAN_RSA_PRIVATE_KEY` | Provided | Generate a 2048-bit RSA key pair (see `sample.env`) |
+| `VISITRAN_RSA_PUBLIC_KEY` | Provided | Derived from the private key above |
-```bash
-docker compose down -v
-```
+
-### Option 2: Direct Installation (localhost)
+
+Local development (without Docker)
For development or when you want full control over each component.
@@ -137,39 +120,27 @@ For development or when you want full control over each component.
**Backend**
```bash
-# Clone the repository
git clone https://github.com/Zipstack/visitran.git
cd visitran
-
-# Set up environment variables
cp backend/sample.env backend/.env
```
-**Edit `backend/.env`** for local development:
+Edit `backend/.env` for local development:
| Variable | Required | Value for localhost |
|----------|----------|---------------------|
-| `SECRET_KEY` | Yes | Default provided — replace in production |
-| `VISITRAN_ENCRYPTION_KEY` | Yes | Default provided — replace in production |
-| `DB_HOST` | No | Leave **empty** for SQLite (no PostgreSQL needed), or `localhost` if you have PostgreSQL running |
+| `DB_HOST` | No | Leave **empty** for SQLite, or `localhost` for local PostgreSQL |
| `REDIS_HOST` | Yes | `localhost` |
-| `DB_SAMPLE_HOST` | No | `localhost` (to enable sample project — requires PostgreSQL) or leave empty to skip |
+| `DB_SAMPLE_HOST` | No | `localhost` (requires PostgreSQL) or leave empty to skip |
-> **Important for localhost:** Change `DB_HOST=postgres` → `DB_HOST=` (empty for SQLite) or `DB_HOST=localhost` (for local PostgreSQL). Change `REDIS_HOST=redis` → `REDIS_HOST=localhost`.
+> **Important:** Change `DB_HOST=postgres` → `DB_HOST=` (empty) and `REDIS_HOST=redis` → `REDIS_HOST=localhost`.
```bash
-# Install backend dependencies
cd backend
pip install uv
uv sync
-
-# Activate virtual environment
source .venv/bin/activate
-
-# Run database migrations
python manage.py migrate
-
-# Start backend server (port 8000)
python manage.py runserver
```
@@ -177,27 +148,14 @@ python manage.py runserver
```bash
cd frontend
-
-# Set up environment variables
cp sample.env .env
-```
-
-**Edit `frontend/.env`** if needed:
-
-| Variable | Default | Description |
-|----------|---------|-------------|
-| `REACT_APP_BACKEND_URL` | `http://localhost:8000` | Backend API URL |
-| `REACT_APP_SOCKET_SERVICE_BASE_URL` | `http://localhost:4000` | WebSocket server URL |
-
-```bash
-# Install dependencies
npm install
-
-# Start dev server (port 3000)
npm start
```
-Open `http://localhost:3000` in your browser and **sign up** to create your account.
+Open [http://localhost:3000](http://localhost:3000) and **sign up** to create your account.
+
+
## Project Structure
diff --git a/backend/backend/core/routers/chat/views.py b/backend/backend/core/routers/chat/views.py
index 02b67ce..9bd8334 100644
--- a/backend/backend/core/routers/chat/views.py
+++ b/backend/backend/core/routers/chat/views.py
@@ -107,7 +107,8 @@ def fetch_token_balance(
f"Token balance check passed for organization {organization.organization_id}. "
f"Required: {tokens_required}, Available: {balance_info.get('current_balance', 0)}"
)
- except Exception:
+ except ImportError:
+ # OSS mode: pluggable_apps not installed, skip billing check
pass
def persist_prompt(self, request: Request, project_id: str, *args, **kwargs) -> Response:
diff --git a/backend/backend/server/settings/1prod.py b/backend/backend/server/settings/1prod.py
deleted file mode 100644
index 43af3f2..0000000
--- a/backend/backend/server/settings/1prod.py
+++ /dev/null
@@ -1,284 +0,0 @@
-"""
-Django settings for server project.
-
-Generated by 'django-admin startproject' using Django 5.0.4.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/5.0/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/5.0/ref/settings/
-"""
-
-import json
-import tempfile
-from urllib.parse import quote_plus
-
-from google.oauth2 import service_account
-
-from backend.server.settings.base import *
-from pluggable_apps.utils.common_utils import CommonUtils
-
-PATH_PREFIX = os.environ.get("PATH_PREFIX", "/api/v1").strip("/")
-
-# Connector OAuth redirection
-SOCIAL_AUTH_REDIRECT_IS_HTTPS = True
-
-DEFAULT_TRIAL_IN_DAYS = os.environ.get("DEFAULT_TRIAL_IN_DAYS", 30)
-
-# To get the actual PROTOCOL and HOST if django behind a proxy
-USE_X_FORWARDED_HOST = True
-PROXY_PROTOCOL = os.environ.get("PROXY_PROTOCOL", "http") # Default to "http"
-SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", PROXY_PROTOCOL)
-
-AUTH_ENABLED = CommonUtils.str_to_bool(os.environ.get("AUTH_ENABLED", "True"))
-ZIPSTACK_ID_DOMAIN = os.environ.get("ZIPSTACK_ID_DOMAIN")
-ZIPSTACK_ID_CLIENT_ID = os.environ.get("ZIPSTACK_ID_CLIENT_ID")
-ZIPSTACK_ID_CLIENT_SECRET = os.environ.get("ZIPSTACK_ID_CLIENT_SECRET")
-TOKEN_CLIENT_ID = os.environ.get("TOKEN_CLIENT_ID")
-TOKEN_CLIENT_SECRET = os.environ.get("TOKEN_CLIENT_SECRET")
-
-# scalekit cred
-SCALEKIT_ENVIRONMENT_URL = os.environ.get("SCALEKIT_ENVIRONMENT_URL")
-SCALEKIT_ENVIRONMENT_ID = os.environ.get("SCALEKIT_ENVIRONMENT_ID")
-SCALEKIT_CLIENT_ID = os.environ.get("SCALEKIT_CLIENT_ID")
-SCALEKIT_CLIENT_SECRET = os.environ.get("SCALEKIT_CLIENT_SECRET")
-
-REDIS_USER = os.environ.get("REDIS_USER", "default")
-REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD", "")
-REDIS_HOST = os.environ.get("REDIS_HOST", "localhost")
-REDIS_PORT = os.environ.get("REDIS_PORT", "6379")
-REDIS_DB = os.environ.get("REDIS_DB", "")
-
-DB_NAME = os.environ.get("DB_NAME", "postgres")
-DB_USER = os.environ.get("DB_USER", "postgres")
-DB_HOST = os.environ.get("DB_HOST", "localhost")
-DB_PASSWORD = os.environ.get("DB_PASSWORD", "postgres")
-DB_PORT = os.environ.get("DB_PORT", 5432)
-
-TOKEN_EXPIRATION_TIME_IN_SECOND = os.environ.get("TOKEN_EXPIRATION_TIME_IN_SECOND", 86400)
-CACHE_TTL_SEC = os.environ.get("CACHE_TTL_SEC", 10800)
-
-SESSION_EXPIRATION_TIME_IN_SECOND = os.environ.get("SESSION_EXPIRATION_TIME_IN_SECOND", 3600)
-
-# Application definition
-# Remove OSS scheduler — cloud uses pluggable_apps.job_scheduler (same DB label)
-SHARED_APPS = [app for app in SHARED_APPS if app != "backend.core.scheduler.apps.SchedulerConfig"]
-SHARED_APPS += [
- "plugins",
- "pluggable_apps.tenant_account",
- "pluggable_apps.job_scheduler",
- "pluggable_apps.user_access_control",
- "pluggable_apps.subscriptions",
- "pluggable_apps.slack_integration",
- "pluggable_apps.project_sharing",
-]
-
-TENANT_APPS = []
-
-INSTALLED_APPS = SHARED_APPS + [app for app in TENANT_APPS if app not in SHARED_APPS]
-
-CUSTOM_AUTH_MIDDLEWARE = "pluggable_apps.middleware.custom_auth_middleware.CustomAuthMiddleware"
-CUSTOM_CSRF_MIDDLEWARE = "pluggable_apps.middleware.custom_csrf_middleware.CustomCsrfMiddleware"
-SUBSCRIPTION_MIDDLEWARE = "plugins.subscriptions.trials.middleware.SubscriptionMiddleware"
-DEFAULT_MODEL_BACKEND = "django.contrib.auth.backends.ModelBackend"
-LOGGING_MIDDLEWARE = "backend.core.middlewares.log_aggregator.LogAggregatorMiddleware"
-LOGGING_CONFIGURATION_MIDDLEWARE = "backend.core.middlewares.log_configuration.LogConfigurationMiddleware"
-
-MIDDLEWARE = [
- "corsheaders.middleware.CorsMiddleware",
- "django.middleware.common.CommonMiddleware",
- "django.middleware.security.SecurityMiddleware",
- "django.contrib.sessions.middleware.SessionMiddleware",
- CUSTOM_CSRF_MIDDLEWARE,
- "django.middleware.csrf.CsrfViewMiddleware",
- "django.contrib.auth.middleware.AuthenticationMiddleware",
- "django.contrib.messages.middleware.MessageMiddleware",
- "django.middleware.clickjacking.XFrameOptionsMiddleware",
- # LOGGING_CONFIGURATION_MIDDLEWARE,
- # LOGGING_MIDDLEWARE,
- CUSTOM_AUTH_MIDDLEWARE,
- SUBSCRIPTION_MIDDLEWARE,
-]
-
-# Allow all subdomains under visitran.com dynamically
-CORS_ALLOWED_ORIGIN_REGEXES = [r"^https://.*\.visitran\.com$"] # Allows any subdomain of visitran.com
-
-TENANT_SUBFOLDER_PREFIX = f"{PATH_PREFIX}/visitran"
-# SHOW_PUBLIC_IF_NO_TENANT_FOUND = True
-#
-# PUBLIC_SCHEMA_URLCONF = "pluggable_apps.account.public_urls"
-TEMPLATES = [
- {
- "BACKEND": "django.template.backends.django.DjangoTemplates",
- "DIRS": [],
- "APP_DIRS": True,
- "OPTIONS": {
- "context_processors": [
- "django.template.context_processors.debug",
- "django.template.context_processors.request",
- "django.contrib.auth.context_processors.auth",
- "django.contrib.messages.context_processors.messages",
- ],
- },
- },
-]
-
-# Database
-# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
-
-DB_ENGINE = "django.db.backends.postgresql"
-
-DATABASES = {
- "default": {
- "ENGINE": DB_ENGINE,
- "NAME": f"{DB_NAME}",
- "USER": f"{DB_USER}",
- "HOST": f"{DB_HOST}",
- "PASSWORD": f"{DB_PASSWORD}",
- "PORT": f"{DB_PORT}",
- "ATOMIC_REQUESTS": True,
- "CONN_MAX_AGE": 120,
- "OPTIONS": {
- "options": "-c timezone=UTC",
- },
- },
- "postgres_db": {
- "ENGINE": "django.db.backends.postgresql",
- "NAME": os.environ.get("DB_SAMPLE_DBNAME"),
- "USER": os.environ.get("DB_SAMPLE_USER"),
- "PASSWORD": os.environ.get("DB_SAMPLE_PASSWORD"),
- "HOST": os.environ.get("DB_SAMPLE_HOST"),
- "PORT": os.environ.get("DB_SAMPLE_PORT"),
- "OPTIONS": {
- "options": f"-c search_path={os.environ.get('DB_SAMPLE_SCHEMA')} -c timezone=UTC",
- },
- },
-}
-
-CACHES = {
- "default": {
- "BACKEND": "django_redis.cache.RedisCache",
- "LOCATION": f"redis://{REDIS_HOST}:{REDIS_PORT}",
- "OPTIONS": {
- "CLIENT_CLASS": "django_redis.client.DefaultClient",
- "SERIALIZER": "django_redis.serializers.json.JSONSerializer",
- "DB": REDIS_DB,
- "USERNAME": REDIS_USER,
- "PASSWORD": REDIS_PASSWORD,
- },
- "KEY_FUNCTION": "pluggable_apps.utils.cache_service.custom_key_function",
- }
-}
-
-# Password validation
-# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
-
-AUTH_PASSWORD_VALIDATORS = [
- {
- "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
- },
- {
- "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
- },
- {
- "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
- },
- {
- "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
- },
-]
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/5.0/howto/static-files/
-STATIC_URL = f"/{PATH_PREFIX}/static/"
-
-AUTH_USER_MODEL = "core.User"
-
-# These paths will work without authentication
-WHITELISTED_PATHS_LIST = [
- "/login",
- "/home",
- "/callback",
- "/favicon.ico",
- "/logout",
- "/signup",
- "/static",
- "/health",
- "/webhooks/stripe-webhook/",
- "/payments/stripe-webhook/",
- "webhooks/stripe/",
-]
-WHITELISTED_PATHS = [f"/{PATH_PREFIX}{PATH}" for PATH in WHITELISTED_PATHS_LIST]
-
-GOOGLE_APPLICATION_CREDENTIALS_JSON = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_JSON")
-
-# Set GCP configuration variables (needed regardless of credentials)
-GCP_BUCKET = os.getenv("GCP_BUCKET", "visitran-uploads")
-GCP_PROJECT_ID = os.getenv("GCP_PROJECT_ID")
-GS_BUCKET_NAME = os.getenv("GCP_BUCKET", "visitran-uploads")
-
-# Only process credentials if actually provided (not None or empty string)
-if GOOGLE_APPLICATION_CREDENTIALS_JSON and GOOGLE_APPLICATION_CREDENTIALS_JSON.strip():
- # Parse the JSON string
- try:
- credentials_info = json.loads(GOOGLE_APPLICATION_CREDENTIALS_JSON)
- except json.JSONDecodeError as e:
- raise RuntimeError(f"Failed to decode JSON credentials: {e}")
-
- # Create a temporary file to store the credentials
- temp_file = tempfile.NamedTemporaryFile(delete=False)
- try:
- with open(temp_file.name, "w") as f:
- json.dump(credentials_info, f)
- # Set the environment variable to the path of the temporary file
- os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = temp_file.name
- except IOError as e:
- raise RuntimeError(f"Failed to write credentials to temporary file: {e}")
-
- # Set up Google Cloud Storage credentials
- credentials = service_account.Credentials.from_service_account_info(credentials_info)
-else:
- # No credentials provided
- credentials = None
-
-# Only use GCS if credentials are configured
-if credentials:
- DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
- GS_CREDENTIALS = credentials
- GS_DEFAULT_ACL = None
-else:
- # Fallback to local file storage if GCS credentials not provided
- DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
- GS_CREDENTIALS = None
- GS_DEFAULT_ACL = None
-
-
-CELERY_RESULT_BACKEND = f"db+postgresql://{DB_USER}:{quote_plus(DB_PASSWORD)}" f"@{DB_HOST}:{DB_PORT}/{DB_NAME}"
-CELERY_ACCEPT_CONTENT = ["json"]
-CELERY_TASK_SERIALIZER = "json"
-CELERY_RESULT_SERIALIZER = "json"
-CELERY_TIMEZONE = "UTC"
-CELERY_TASK_MAX_RETRIES = 3
-CELERY_TASK_RETRY_BACKOFF = 60 # Time in seconds before retrying the task
-CELERY_TASK_ACKS_LATE = True
-CELERY_TASK_REJECT_ON_WORKER_LOST = True
-CELERY_IMPORTS = ["pluggable_apps.job_scheduler.celery_tasks"]
-
-# This could be either RedisManager or eventlet
-WEBSOCKET_MANAGER = os.environ.get("SOCKET_MANAGER", "RedisManager")
-
-STRIPE_ENDPOINT_SECRET= os.environ.get("STRIPE_ENDPOINT_SECRET")
-STRIPE_SECRET_KEY= os.environ.get("STRIPE_SECRET_KEY")
-STRIPE_PUBLISHABLE_KEY= os.environ.get("STRIPE_PUBLISHABLE_KEY")
-
-VISITRAN_RSA_PRIVATE_KEY= os.environ.get("VISITRAN_RSA_PRIVATE_KEY")
-
-VISITRAN_RSA_PUBLIC_KEY= os.environ.get("VISITRAN_RSA_PUBLIC_KEY")
-
-# Django REST Framework: Use custom auth that skips CSRF for Bearer token requests
-REST_FRAMEWORK = {
- "DEFAULT_AUTHENTICATION_CLASSES": [
- "backend.core.authentication.CsrfExemptSessionAuthentication",
- ],
-}