From 839ff688ddd6c892ba4ded975083830644087e24 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 24 Mar 2026 16:35:39 +0530 Subject: [PATCH 1/3] fix: remove cloud-only 1prod.py from OSS repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- backend/backend/server/settings/1prod.py | 284 ----------------------- 1 file changed, 284 deletions(-) delete mode 100644 backend/backend/server/settings/1prod.py 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", - ], -} From b85dbfa2afcf97d2d2008964dea2fdc42e3ffc05 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 24 Mar 2026 16:35:51 +0530 Subject: [PATCH 2/3] docs: simplify getting started instructions in README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 100 ++++++++++++++++-------------------------------------- 1 file changed, 29 insertions(+), 71 deletions(-) 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 From 729e2c48e8fc3e78d6c807a9ea9a464c8e3648e5 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 24 Mar 2026 16:36:05 +0530 Subject: [PATCH 3/3] fix(cloud): prevent credit check from being silently bypassed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- backend/backend/core/routers/chat/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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: