diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..689146d --- /dev/null +++ b/.env.example @@ -0,0 +1,123 @@ +############ +# Secrets +# YOU MUST CHANGE THESE BEFORE GOING INTO PRODUCTION +############ + +POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password +JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long +ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE +SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q +DASHBOARD_USERNAME=supabase +DASHBOARD_PASSWORD=this_password_is_insecure_and_should_be_updated +SECRET_KEY_BASE=UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq +VAULT_ENC_KEY=your-encryption-key-32-chars-min + + +############ +# Database - You can change these to any PostgreSQL database that has logical replication enabled. +############ + +POSTGRES_HOST=db +POSTGRES_DB=postgres +POSTGRES_PORT=5432 +# default user is postgres + + +############ +# Supavisor -- Database pooler +############ +POOLER_PROXY_PORT_TRANSACTION=6543 +POOLER_DEFAULT_POOL_SIZE=20 +POOLER_MAX_CLIENT_CONN=100 +POOLER_TENANT_ID=your-tenant-id + + +############ +# API Proxy - Configuration for the Kong Reverse proxy. +############ + +KONG_HTTP_PORT=8000 +KONG_HTTPS_PORT=8443 + + +############ +# API - Configuration for PostgREST. +############ + +PGRST_DB_SCHEMAS=public,storage,graphql_public + + +############ +# Auth - Configuration for the GoTrue authentication server. +############ + +## General +SITE_URL=http://localhost:3000 +ADDITIONAL_REDIRECT_URLS= +JWT_EXPIRY=3600 +DISABLE_SIGNUP=false +API_EXTERNAL_URL=http://localhost:8000 + +## Mailer Config +MAILER_URLPATHS_CONFIRMATION="/auth/v1/verify" +MAILER_URLPATHS_INVITE="/auth/v1/verify" +MAILER_URLPATHS_RECOVERY="/auth/v1/verify" +MAILER_URLPATHS_EMAIL_CHANGE="/auth/v1/verify" + +## Email auth +ENABLE_EMAIL_SIGNUP=true +ENABLE_EMAIL_AUTOCONFIRM=true +SMTP_ADMIN_EMAIL=admin@example.com +SMTP_HOST=supabase-mail +SMTP_PORT=2500 +SMTP_USER=fake_mail_user +SMTP_PASS=fake_mail_password +SMTP_SENDER_NAME=fake_sender +ENABLE_ANONYMOUS_USERS=false + +## Phone auth +ENABLE_PHONE_SIGNUP=true +ENABLE_PHONE_AUTOCONFIRM=true + + +############ +# Studio - Configuration for the Dashboard +############ + +STUDIO_DEFAULT_ORGANIZATION=Default Organization +STUDIO_DEFAULT_PROJECT=Default Project + +STUDIO_PORT=3000 +# replace if you intend to use Studio outside of localhost +SUPABASE_PUBLIC_URL=http://localhost:8000 + +# Enable webp support +IMGPROXY_ENABLE_WEBP_DETECTION=true + +# Add your OpenAI API key to enable SQL Editor Assistant +OPENAI_API_KEY= + + +############ +# Functions - Configuration for Functions +############ +# NOTE: VERIFY_JWT applies to all functions. Per-function VERIFY_JWT is not supported yet. +FUNCTIONS_VERIFY_JWT=false + + +############ +# Logs - Configuration for Logflare +# Please refer to https://supabase.com/docs/reference/self-hosting-analytics/introduction +############ + +LOGFLARE_LOGGER_BACKEND_API_KEY=your-super-secret-and-long-logflare-key + +# Change vector.toml sinks to reflect this change +LOGFLARE_API_KEY=your-super-secret-and-long-logflare-key + +# Docker socket location - this value will differ depending on your OS +DOCKER_SOCKET_LOCATION=/var/run/docker.sock + +# Google Cloud Project details +GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID +GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example index f17bb4b..27eefa0 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -121,3 +121,15 @@ DOCKER_SOCKET_LOCATION=/var/run/docker.sock # Google Cloud Project details GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER + + +## FLOWSINT-API + +SUPABASE_URL=http://localhost:8000 +SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE + +## FLOWSINT-WEB + +NEXT_PUBLIC_AUTH_REDIRECT=http://localhost:3000/auth/callback +NEXT_PUBLIC_SUPABASE_URL=https://tylhnsayytaoaaiqsgdp.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InR5bGhuc2F5eXRhb2FhaXFzZ2RwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzgzMzY3MzAsImV4cCI6MjA1MzkxMjczMH0.iVcpz8RpOgzVSamp_tNQQmjdLL9_Olx6m4LfsLVw1bg \ No newline at end of file diff --git a/backend/docker-compose.s3.yml b/backend/docker-compose.s3.yml deleted file mode 100644 index 043691a..0000000 --- a/backend/docker-compose.s3.yml +++ /dev/null @@ -1,94 +0,0 @@ -services: - - minio: - image: minio/minio - ports: - - '9000:9000' - - '9001:9001' - environment: - MINIO_ROOT_USER: supa-storage - MINIO_ROOT_PASSWORD: secret1234 - command: server --console-address ":9001" /data - healthcheck: - test: [ "CMD", "curl", "-f", "http://minio:9000/minio/health/live" ] - interval: 2s - timeout: 10s - retries: 5 - volumes: - - ./volumes/storage:/data:z - - minio-createbucket: - image: minio/mc - depends_on: - minio: - condition: service_healthy - entrypoint: > - /bin/sh -c " - /usr/bin/mc alias set supa-minio http://minio:9000 supa-storage secret1234; - /usr/bin/mc mb supa-minio/stub; - exit 0; - " - - storage: - container_name: supabase-storage - image: supabase/storage-api:v1.11.13 - depends_on: - db: - # Disable this if you are using an external Postgres database - condition: service_healthy - rest: - condition: service_started - imgproxy: - condition: service_started - minio: - condition: service_healthy - healthcheck: - test: - [ - "CMD", - "wget", - "--no-verbose", - "--tries=1", - "--spider", - "http://localhost:5000/status" - ] - timeout: 5s - interval: 5s - retries: 3 - restart: unless-stopped - environment: - ANON_KEY: ${ANON_KEY} - SERVICE_KEY: ${SERVICE_ROLE_KEY} - POSTGREST_URL: http://rest:3000 - PGRST_JWT_SECRET: ${JWT_SECRET} - DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} - FILE_SIZE_LIMIT: 52428800 - STORAGE_BACKEND: s3 - GLOBAL_S3_BUCKET: stub - GLOBAL_S3_ENDPOINT: http://minio:9000 - GLOBAL_S3_PROTOCOL: http - GLOBAL_S3_FORCE_PATH_STYLE: true - AWS_ACCESS_KEY_ID: supa-storage - AWS_SECRET_ACCESS_KEY: secret1234 - AWS_DEFAULT_REGION: stub - FILE_STORAGE_BACKEND_PATH: /var/lib/storage - TENANT_ID: stub - # TODO: https://github.com/supabase/storage-api/issues/55 - REGION: stub - ENABLE_IMAGE_TRANSFORMATION: "true" - IMGPROXY_URL: http://imgproxy:5001 - volumes: - - ./volumes/storage:/var/lib/storage:z - - imgproxy: - container_name: supabase-imgproxy - image: darthsim/imgproxy:v3.8.0 - healthcheck: - test: [ "CMD", "imgproxy", "health" ] - timeout: 5s - interval: 5s - retries: 3 - environment: - IMGPROXY_BIND: ":5001" - IMGPROXY_USE_ETAG: "true" - IMGPROXY_ENABLE_WEBP_DETECTION: ${IMGPROXY_ENABLE_WEBP_DETECTION} diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..b119b21 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,91 @@ +name: flowsint + +services: + # Traefik reverse proxy + reverse-proxy: + image: traefik:v3.1 + container_name: traefik-reverse-proxy + command: + - "--api.insecure=true" + - "--providers.docker" + - "--entrypoints.ws.address=:80" + - "--providers.file.watch=true" + ports: + - "80:80" + - "8080:8080" + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - traefik + + # Next.js application + flowsint-web: + build: + context: flowsint-web + dockerfile: Dockerfile + container_name: flowsint-web + ports: + - "3000:3000" + env_file: + - .env + networks: + - traefik + labels: + - "traefik.enable=true" + - "traefik.http.routers.nextjs.rule=Host(`app.flowsint.localhost`)" + - "traefik.http.services.nextjs.loadbalancer.server.port=3000" + + # Flask-like application with Celery + flowsint-api: + build: + context: flowsint-api + dockerfile: Dockerfile + container_name: flowsint-api + ports: + - "5000:5000" + depends_on: + - redis + environment: + - REDIS_URL=redis://redis:6379/0 + - ENV=prod + command: ["/app/entrypoint.sh", "app"] + restart: always + networks: + - traefik + labels: + - "traefik.enable=true" + - "traefik.http.routers.app.rule=Host(`api.flowsint.localhost`)" + - "traefik.http.services.app.loadbalancer.server.port=5000" + + celery: + build: + context: flowsint-api + dockerfile: Dockerfile + container_name: celery-worker + depends_on: + - redis + - flowsint-api + environment: + - REDIS_URL=redis://redis:6379/0 + - ENV=prod + command: ["/app/entrypoint.sh", "celery"] + restart: always + networks: + - traefik + + redis: + image: "redis:alpine" + container_name: redis-cache + ports: + - "6379:6379" + networks: + - traefik + +networks: + traefik: + name: traefik + driver: bridge + +volumes: + db-config: \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 038f52f..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,22 +0,0 @@ -version: '3.8' - -services: - backend: - build: ./flowsint-api - ports: - - "5000:5000" - volumes: - - ./flowsint-api:/app - environment: - - FLASK_ENV=development - - frontend: - build: ./flowsint-web - ports: - - "3000:3000" - volumes: - - ./flowsint-web:/app - depends_on: - - backend - environment: - - NEXT_PUBLIC_API_URL=http://localhost:5000 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1ae2dc1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,575 @@ +name: flowsint + +services: + # Traefik reverse proxy + reverse-proxy: + image: traefik:v3.1 + container_name: traefik-reverse-proxy + command: + - "--api.insecure=true" + - "--providers.docker" + - "--entrypoints.ws.address=:80" + - "--providers.file.watch=true" + ports: + - "80:80" + - "8080:8080" + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - traefik + + # Next.js application + flowsint-web: + build: + context: flowsint-web + dockerfile: Dockerfile + container_name: flowsint-web + ports: + - "3000:3000" + env_file: + - .env + networks: + - traefik + labels: + - "traefik.enable=true" + - "traefik.http.routers.nextjs.rule=Host(`app.flowsint.localhost`)" + - "traefik.http.services.nextjs.loadbalancer.server.port=3000" + + # Flask-like application with Celery + flowsint-api: + build: + context: flowsint-api + dockerfile: Dockerfile + container_name: flowsint-api + ports: + - "5000:5000" + depends_on: + - redis + environment: + - REDIS_URL=redis://redis:6379/0 + - ENV=prod + command: ["/app/entrypoint.sh", "app"] + restart: always + networks: + - traefik + labels: + - "traefik.enable=true" + - "traefik.http.routers.app.rule=Host(`api.flowsint.localhost`)" + - "traefik.http.services.app.loadbalancer.server.port=5000" + + celery: + build: + context: flowsint-api + dockerfile: Dockerfile + container_name: celery-worker + depends_on: + - redis + - flowsint-api + environment: + - REDIS_URL=redis://redis:6379/0 + - ENV=prod + command: ["/app/entrypoint.sh", "celery"] + restart: always + networks: + - traefik + + redis: + image: "redis:alpine" + container_name: redis-cache + ports: + - "6379:6379" + networks: + - traefik + + # Supabase services + studio: + container_name: supabase-studio + image: supabase/studio:20250224-d10db0f + restart: unless-stopped + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://studio:3000/api/platform/profile').then((r) => {if (r.status !== 200) throw new Error(r.status)})" + ] + timeout: 10s + interval: 5s + retries: 3 + depends_on: + analytics: + condition: service_healthy + environment: + STUDIO_PG_META_URL: http://meta:8080 + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + DEFAULT_ORGANIZATION_NAME: ${STUDIO_DEFAULT_ORGANIZATION} + DEFAULT_PROJECT_NAME: ${STUDIO_DEFAULT_PROJECT} + OPENAI_API_KEY: ${OPENAI_API_KEY:-} + SUPABASE_URL: http://kong:8000 + SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL} + SUPABASE_ANON_KEY: ${ANON_KEY} + SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY} + AUTH_JWT_SECRET: ${JWT_SECRET} + LOGFLARE_API_KEY: ${LOGFLARE_API_KEY} + LOGFLARE_URL: http://analytics:4000 + NEXT_PUBLIC_ENABLE_LOGS: true + NEXT_ANALYTICS_BACKEND_PROVIDER: postgres + networks: + - traefik + labels: + - "traefik.enable=true" + - "traefik.http.routers.studio.rule=Host(`studio.flowsint.localhost`)" + - "traefik.http.services.studio.loadbalancer.server.port=3000" + + kong: + container_name: supabase-kong + image: kong:2.8.1 + restart: unless-stopped + ports: + - ${KONG_HTTP_PORT}:8000/tcp + - ${KONG_HTTPS_PORT}:8443/tcp + volumes: + - ./backend/volumes/api/kong.yml:/home/kong/temp.yml:ro,z + depends_on: + analytics: + condition: service_healthy + environment: + KONG_DATABASE: "off" + KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml + KONG_DNS_ORDER: LAST,A,CNAME + KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth + KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k + KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k + SUPABASE_ANON_KEY: ${ANON_KEY} + SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY} + DASHBOARD_USERNAME: ${DASHBOARD_USERNAME} + DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD} + entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start' + networks: + - traefik + labels: + - "traefik.enable=true" + - "traefik.http.routers.kong.rule=Host(`kong.flowsint.localhost`)" + - "traefik.http.services.kong.loadbalancer.server.port=8000" + + auth: + container_name: supabase-auth + image: supabase/gotrue:v2.170.0 + restart: unless-stopped + healthcheck: + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://localhost:9999/health" + ] + timeout: 5s + interval: 5s + retries: 3 + depends_on: + db: + condition: service_healthy + analytics: + condition: service_healthy + environment: + GOTRUE_API_HOST: 0.0.0.0 + GOTRUE_API_PORT: 9999 + API_EXTERNAL_URL: ${API_EXTERNAL_URL} + GOTRUE_DB_DRIVER: postgres + GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + GOTRUE_SITE_URL: ${SITE_URL} + GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS} + GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP} + GOTRUE_JWT_ADMIN_ROLES: service_role + GOTRUE_JWT_AUD: authenticated + GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated + GOTRUE_JWT_EXP: ${JWT_EXPIRY} + GOTRUE_JWT_SECRET: ${JWT_SECRET} + GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP} + GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: ${ENABLE_ANONYMOUS_USERS} + GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM} + GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL} + GOTRUE_SMTP_HOST: ${SMTP_HOST} + GOTRUE_SMTP_PORT: ${SMTP_PORT} + GOTRUE_SMTP_USER: ${SMTP_USER} + GOTRUE_SMTP_PASS: ${SMTP_PASS} + GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME} + GOTRUE_MAILER_URLPATHS_INVITE: ${MAILER_URLPATHS_INVITE} + GOTRUE_MAILER_URLPATHS_CONFIRMATION: ${MAILER_URLPATHS_CONFIRMATION} + GOTRUE_MAILER_URLPATHS_RECOVERY: ${MAILER_URLPATHS_RECOVERY} + GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: ${MAILER_URLPATHS_EMAIL_CHANGE} + GOTRUE_EXTERNAL_PHONE_ENABLED: ${ENABLE_PHONE_SIGNUP} + GOTRUE_SMS_AUTOCONFIRM: ${ENABLE_PHONE_AUTOCONFIRM} + networks: + - traefik + + rest: + container_name: supabase-rest + image: postgrest/postgrest:v12.2.8 + restart: unless-stopped + depends_on: + db: + condition: service_healthy + analytics: + condition: service_healthy + environment: + PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS} + PGRST_DB_ANON_ROLE: anon + PGRST_JWT_SECRET: ${JWT_SECRET} + PGRST_DB_USE_LEGACY_GUCS: "false" + PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET} + PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXPIRY} + command: + [ + "postgrest" + ] + networks: + - traefik + + realtime: + container_name: realtime-dev.supabase-realtime + image: supabase/realtime:v2.34.40 + restart: unless-stopped + depends_on: + db: + condition: service_healthy + analytics: + condition: service_healthy + healthcheck: + test: + [ + "CMD", + "curl", + "-sSfL", + "--head", + "-o", + "/dev/null", + "-H", + "Authorization: Bearer ${ANON_KEY}", + "http://localhost:4000/api/tenants/realtime-dev/health" + ] + timeout: 5s + interval: 5s + retries: 3 + environment: + PORT: 4000 + DB_HOST: ${POSTGRES_HOST} + DB_PORT: ${POSTGRES_PORT} + DB_USER: supabase_admin + DB_PASSWORD: ${POSTGRES_PASSWORD} + DB_NAME: ${POSTGRES_DB} + DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime' + DB_ENC_KEY: supabaserealtime + API_JWT_SECRET: ${JWT_SECRET} + SECRET_KEY_BASE: ${SECRET_KEY_BASE} + ERL_AFLAGS: -proto_dist inet_tcp + DNS_NODES: "''" + RLIMIT_NOFILE: "10000" + APP_NAME: realtime + SEED_SELF_HOST: true + RUN_JANITOR: true + networks: + - traefik + + storage: + container_name: supabase-storage + image: supabase/storage-api:v1.19.3 + restart: unless-stopped + volumes: + - ./backend/volumes/storage:/var/lib/storage:z + healthcheck: + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://storage:5000/status" + ] + timeout: 5s + interval: 5s + retries: 3 + depends_on: + db: + condition: service_healthy + rest: + condition: service_started + imgproxy: + condition: service_started + environment: + ANON_KEY: ${ANON_KEY} + SERVICE_KEY: ${SERVICE_ROLE_KEY} + POSTGREST_URL: http://rest:3000 + PGRST_JWT_SECRET: ${JWT_SECRET} + DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + FILE_SIZE_LIMIT: 52428800 + STORAGE_BACKEND: file + FILE_STORAGE_BACKEND_PATH: /var/lib/storage + TENANT_ID: stub + REGION: stub + GLOBAL_S3_BUCKET: stub + ENABLE_IMAGE_TRANSFORMATION: "true" + IMGPROXY_URL: http://imgproxy:5001 + networks: + - traefik + + imgproxy: + container_name: supabase-imgproxy + image: darthsim/imgproxy:v3.8.0 + restart: unless-stopped + volumes: + - ./backend/volumes/storage:/var/lib/storage:z + healthcheck: + test: + [ + "CMD", + "imgproxy", + "health" + ] + timeout: 5s + interval: 5s + retries: 3 + environment: + IMGPROXY_BIND: ":5001" + IMGPROXY_LOCAL_FILESYSTEM_ROOT: / + IMGPROXY_USE_ETAG: "true" + IMGPROXY_ENABLE_WEBP_DETECTION: ${IMGPROXY_ENABLE_WEBP_DETECTION} + networks: + - traefik + + meta: + container_name: supabase-meta + image: supabase/postgres-meta:v0.86.1 + restart: unless-stopped + depends_on: + db: + condition: service_healthy + analytics: + condition: service_healthy + environment: + PG_META_PORT: 8080 + PG_META_DB_HOST: ${POSTGRES_HOST} + PG_META_DB_PORT: ${POSTGRES_PORT} + PG_META_DB_NAME: ${POSTGRES_DB} + PG_META_DB_USER: supabase_admin + PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD} + networks: + - traefik + + functions: + container_name: supabase-edge-functions + image: supabase/edge-runtime:v1.67.2 + restart: unless-stopped + volumes: + - ./backend/volumes/functions:/home/deno/functions:Z + depends_on: + analytics: + condition: service_healthy + environment: + JWT_SECRET: ${JWT_SECRET} + SUPABASE_URL: http://kong:8000 + SUPABASE_ANON_KEY: ${ANON_KEY} + SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY} + SUPABASE_DB_URL: postgresql://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + VERIFY_JWT: "${FUNCTIONS_VERIFY_JWT}" + command: + [ + "start", + "--main-service", + "/home/deno/functions/main" + ] + networks: + - traefik + labels: + - "traefik.enable=true" + - "traefik.http.routers.functions.rule=Host(`functions.flowsint.localhost`)" + - "traefik.http.services.functions.loadbalancer.server.port=9000" + + analytics: + container_name: supabase-analytics + image: supabase/logflare:1.12.5 + restart: unless-stopped + ports: + - 4000:4000 + healthcheck: + test: + [ + "CMD", + "curl", + "http://localhost:4000/health" + ] + timeout: 5s + interval: 5s + retries: 10 + depends_on: + db: + condition: service_healthy + environment: + LOGFLARE_NODE_HOST: 127.0.0.1 + DB_USERNAME: supabase_admin + DB_DATABASE: _supabase + DB_HOSTNAME: ${POSTGRES_HOST} + DB_PORT: ${POSTGRES_PORT} + DB_PASSWORD: ${POSTGRES_PASSWORD} + DB_SCHEMA: _analytics + LOGFLARE_API_KEY: ${LOGFLARE_API_KEY} + LOGFLARE_SINGLE_TENANT: true + LOGFLARE_SUPABASE_MODE: true + LOGFLARE_MIN_CLUSTER_SIZE: 1 + POSTGRES_BACKEND_URL: postgresql://supabase_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/_supabase + POSTGRES_BACKEND_SCHEMA: _analytics + LOGFLARE_FEATURE_FLAG_OVERRIDE: multibackend=true + networks: + - traefik + + db: + container_name: supabase-db + image: supabase/postgres:15.8.1.049 + restart: unless-stopped + volumes: + - ./backend/volumes/db/realtime.sql:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:Z + - ./backend/volumes/db/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z + - ./backend/volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z + - ./backend/volumes/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z + - ./backend/volumes/db/data:/var/lib/postgresql/data:Z + - ./backend/volumes/db/_supabase.sql:/docker-entrypoint-initdb.d/migrations/97-_supabase.sql:Z + - ./backend/volumes/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z + - ./backend/volumes/db/pooler.sql:/docker-entrypoint-initdb.d/migrations/99-pooler.sql:Z + - db-config:/etc/postgresql-custom + healthcheck: + test: + [ + "CMD", + "pg_isready", + "-U", + "postgres", + "-h", + "localhost" + ] + interval: 5s + timeout: 5s + retries: 10 + depends_on: + vector: + condition: service_healthy + environment: + POSTGRES_HOST: /var/run/postgresql + PGPORT: ${POSTGRES_PORT} + POSTGRES_PORT: ${POSTGRES_PORT} + PGPASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + PGDATABASE: ${POSTGRES_DB} + POSTGRES_DB: ${POSTGRES_DB} + JWT_SECRET: ${JWT_SECRET} + JWT_EXP: ${JWT_EXPIRY} + command: + [ + "postgres", + "-c", + "config_file=/etc/postgresql/postgresql.conf", + "-c", + "log_min_messages=fatal" + ] + networks: + - traefik + + vector: + container_name: supabase-vector + image: timberio/vector:0.28.1-alpine + restart: unless-stopped + volumes: + - ./backend/volumes/logs/vector.yml:/etc/vector/vector.yml:ro,z + - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock:ro,z + healthcheck: + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://vector:9001/health" + ] + timeout: 5s + interval: 5s + retries: 3 + environment: + LOGFLARE_API_KEY: ${LOGFLARE_API_KEY} + command: + [ + "--config", + "/etc/vector/vector.yml" + ] + security_opt: + - "label=disable" + networks: + - traefik + + supavisor: + container_name: supabase-pooler + image: supabase/supavisor:2.4.12 + restart: unless-stopped + ports: + - ${POSTGRES_PORT}:5432 + - ${POOLER_PROXY_PORT_TRANSACTION}:6543 + volumes: + - ./backend/volumes/pooler/pooler.exs:/etc/pooler/pooler.exs:ro,z + healthcheck: + test: + [ + "CMD", + "curl", + "-sSfL", + "--head", + "-o", + "/dev/null", + "http://127.0.0.1:4000/api/health" + ] + interval: 10s + timeout: 5s + retries: 5 + depends_on: + db: + condition: service_healthy + analytics: + condition: service_healthy + environment: + PORT: 4000 + POSTGRES_PORT: ${POSTGRES_PORT} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + DATABASE_URL: ecto://supabase_admin:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/_supabase + CLUSTER_POSTGRES: true + SECRET_KEY_BASE: ${SECRET_KEY_BASE} + VAULT_ENC_KEY: ${VAULT_ENC_KEY} + API_JWT_SECRET: ${JWT_SECRET} + METRICS_JWT_SECRET: ${JWT_SECRET} + REGION: local + ERL_AFLAGS: -proto_dist inet_tcp + POOLER_TENANT_ID: ${POOLER_TENANT_ID} + POOLER_DEFAULT_POOL_SIZE: ${POOLER_DEFAULT_POOL_SIZE} + POOLER_MAX_CLIENT_CONN: ${POOLER_MAX_CLIENT_CONN} + POOLER_POOL_MODE: transaction + command: + [ + "/bin/sh", + "-c", + "/app/bin/migrate && /app/bin/supavisor eval \"$$(cat /etc/pooler/pooler.exs)\" && /app/bin/server" + ] + networks: + - traefik + +networks: + traefik: + name: traefik + driver: bridge + +volumes: + db-config: \ No newline at end of file diff --git a/flowsint-api/docker-compose.yml b/flowsint-api/docker-compose.yml deleted file mode 100644 index 2aa2c33..0000000 --- a/flowsint-api/docker-compose.yml +++ /dev/null @@ -1,28 +0,0 @@ -services: - app: - build: - context: . - dockerfile: Dockerfile - ports: - - "5000:5000" - depends_on: - - redis - environment: - - REDIS_URL=redis://redis:6379/0 - command: ["/app/entrypoint.sh", "app"] - - celery: - build: - context: . - dockerfile: Dockerfile - depends_on: - - redis - - app - environment: - - REDIS_URL=redis://redis:6379/0 - command: ["/app/entrypoint.sh", "celery"] - - redis: - image: "redis:alpine" - ports: - - "6379:6379" diff --git a/flowsint-web/Dockerfile b/flowsint-web/Dockerfile index 408ce55..4db8d4b 100644 --- a/flowsint-web/Dockerfile +++ b/flowsint-web/Dockerfile @@ -1,7 +1,66 @@ -FROM node:18 +# syntax=docker.io/docker/dockerfile:1 + +FROM node:18-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat WORKDIR /app -COPY package.json yarn.lock ./ -RUN pnpm install + +# Install dependencies based on the preferred package manager +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./ +RUN \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ + else echo "Lockfile not found." && exit 1; \ + fi + + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules COPY . . + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +# ENV NEXT_TELEMETRY_DISABLED=1 + +RUN \ + if [ -f yarn.lock ]; then yarn run build; \ + elif [ -f package-lock.json ]; then npm run build; \ + elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ + else echo "Lockfile not found." && exit 1; \ + fi + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production +# Uncomment the following line in case you want to disable telemetry during runtime. +# ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + EXPOSE 3000 -CMD ["yarn", "dev"] + +ENV PORT=3000 + +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/config/next-config-js/output +ENV HOSTNAME="0.0.0.0" +CMD ["node", "server.js"] \ No newline at end of file diff --git a/flowsint-web/next.config.js b/flowsint-web/next.config.js index 2dd01c7..52a13a9 100644 --- a/flowsint-web/next.config.js +++ b/flowsint-web/next.config.js @@ -1,5 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + output: "standalone", experimental: { authInterrupts: true, }, diff --git a/flowsint-web/src/app/auth/confirm/route.ts b/flowsint-web/src/app/auth/confirm/route.ts index 4a46dca..beee77c 100644 --- a/flowsint-web/src/app/auth/confirm/route.ts +++ b/flowsint-web/src/app/auth/confirm/route.ts @@ -22,7 +22,6 @@ export async function GET(request: NextRequest) { redirect(next) } } - // redirect the user to an error page with some instructions redirect('/error') } \ No newline at end of file diff --git a/flowsint-web/src/app/dashboard/page.tsx b/flowsint-web/src/app/dashboard/page.tsx index 57c2009..332f634 100644 --- a/flowsint-web/src/app/dashboard/page.tsx +++ b/flowsint-web/src/app/dashboard/page.tsx @@ -46,7 +46,7 @@ const DashboardPage = () => {
-
+
{ const isRefetching = isRefetchingDocs || isRefetchingSketches return ( - +
-
+
{ - {project.investigations.length === 0 && searchQuery !== "" ? ( + {project.investigations.length === 0 && documents?.length === 0 ? ( - - No sketch yet. + +
+ No sketch yet or document yet. + + + +
) : ( diff --git a/flowsint-web/src/app/dashboard/settings/appearance/appearance-form.tsx b/flowsint-web/src/app/dashboard/settings/appearance/appearance-form.tsx index cd432e8..10636d4 100644 --- a/flowsint-web/src/app/dashboard/settings/appearance/appearance-form.tsx +++ b/flowsint-web/src/app/dashboard/settings/appearance/appearance-form.tsx @@ -48,11 +48,7 @@ export function AppearanceForm() { function onSubmit(data: AppearanceFormValues) { setTheme(data.theme) - toast( -
-        {JSON.stringify(data, null, 2)}
-      
- ) + toast.success("Appearance settings updated.") } return ( diff --git a/flowsint-web/src/app/dashboard/settings/display/display-form.tsx b/flowsint-web/src/app/dashboard/settings/display/display-form.tsx index 9139caa..a5afc10 100644 --- a/flowsint-web/src/app/dashboard/settings/display/display-form.tsx +++ b/flowsint-web/src/app/dashboard/settings/display/display-form.tsx @@ -64,11 +64,7 @@ export function DisplayForm() { }) function onSubmit(data: DisplayFormValues) { - toast( -
-        {JSON.stringify(data, null, 2)}
-      
- ) + toast.success("Display settings updated.") } return ( diff --git a/flowsint-web/src/app/error.tsx b/flowsint-web/src/app/error.tsx index e260a7e..18bcb97 100644 --- a/flowsint-web/src/app/error.tsx +++ b/flowsint-web/src/app/error.tsx @@ -13,7 +13,6 @@ export default function Error({ reset: () => void }) { useEffect(() => { - // Log the error to an error reporting service console.error(error) }, [error]) diff --git a/flowsint-web/src/components/app-sidebar.tsx b/flowsint-web/src/components/app-sidebar.tsx index 464ce21..45ad6f6 100644 --- a/flowsint-web/src/components/app-sidebar.tsx +++ b/flowsint-web/src/components/app-sidebar.tsx @@ -140,7 +140,7 @@ export function AppSidebar({ user, ...props }: AppSidebarProps) { - Preferences + PREFERENCES {preferencesNavItems.map((item) => ( diff --git a/flowsint-web/src/components/dashboard/layout.tsx b/flowsint-web/src/components/dashboard/layout.tsx index 53f4f1d..760b701 100644 --- a/flowsint-web/src/components/dashboard/layout.tsx +++ b/flowsint-web/src/components/dashboard/layout.tsx @@ -1,7 +1,6 @@ import React, { Fragment } from 'react' import { Breadcrumb, - BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, diff --git a/flowsint-web/src/components/dashboard/new-project.tsx b/flowsint-web/src/components/dashboard/new-project.tsx index dfa2c7f..d8c9a91 100644 --- a/flowsint-web/src/components/dashboard/new-project.tsx +++ b/flowsint-web/src/components/dashboard/new-project.tsx @@ -4,7 +4,7 @@ import type React from "react" import { useState } from "react" import { useRouter } from "next/navigation" -import { createNewProject } from "@/lib/actions/projects" +import { createNewProject } from "@/lib/actions/project" import { Button } from "@/components/ui/button" import { Dialog, @@ -17,7 +17,6 @@ import { import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" -import { Badge } from "@/components/ui/badge" import { useFormStatus } from "react-dom" import { toast } from "sonner" diff --git a/flowsint-web/src/components/dashboard/new-sketch.tsx b/flowsint-web/src/components/dashboard/new-sketch.tsx index 2586155..20b9b30 100644 --- a/flowsint-web/src/components/dashboard/new-sketch.tsx +++ b/flowsint-web/src/components/dashboard/new-sketch.tsx @@ -33,7 +33,7 @@ function SubmitButton() { ) } -export default function NewCase({ children }: { children: React.ReactNode }) { +export default function NewCase({ children, noDropDown = false }: { children: React.ReactNode, noDropDown?: boolean }) { const [open, setOpen] = useState(false) const { setOpenUploadModal } = useProjectStore() @@ -52,6 +52,41 @@ export default function NewCase({ children }: { children: React.ReactNode }) { } } + if (noDropDown) return ( + <> + + + + + New sketch + Create a new blank sketch. + +
+
+
+ + +
+
+ + +
+
+ + + + +
+
+
+ + ) return ( <> diff --git a/flowsint-web/src/components/env-indicator.tsx b/flowsint-web/src/components/env-indicator.tsx new file mode 100644 index 0000000..0544040 --- /dev/null +++ b/flowsint-web/src/components/env-indicator.tsx @@ -0,0 +1,24 @@ +"use client" + +import { useEffect, useState } from "react" +import { Badge } from "./ui/badge" +import { cn } from "@/lib/utils" + +export default function EnvIndicator() { + const [isClient, setIsClient] = useState(false) + + useEffect(() => { + setIsClient(true) + }, []) + if (!isClient) return null + const isDevelopment = process.env.NODE_ENV === "development" + + return ( + + {isDevelopment ? "development" : "production"} + + ) +} + diff --git a/flowsint-web/src/components/investigations/individual-modal.tsx b/flowsint-web/src/components/investigations/individual-modal.tsx index c730335..e63b154 100644 --- a/flowsint-web/src/components/investigations/individual-modal.tsx +++ b/flowsint-web/src/components/investigations/individual-modal.tsx @@ -1,4 +1,5 @@ "use client" + import type React from "react" import { useEffect, useState } from "react" import { useIndividual } from "@/lib/hooks/individuals/use-individual" @@ -14,11 +15,11 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Badge } from "@/components/ui/badge" import { Alert, AlertDescription } from "@/components/ui/alert" import { Card, CardContent } from "@/components/ui/card" -import { Pencil, X, Plus, Trash2 } from "lucide-react" +import { Pencil, X, Plus, Trash2, Building2, Mail, MapPin, Phone, Briefcase, ExternalLink } from "lucide-react" import Breaches from "../breach" import { useQueryState } from "nuqs" -import { Building2, Mail, MapPin, Phone, User } from "lucide-react" -import Image from "next/image" +import { Progress } from "@/components/ui/progress" + const IndividualModal = () => { const [individualId, setIndividualId] = useQueryState("individual_id") const { individual, isLoading } = useIndividual(individualId) @@ -32,8 +33,8 @@ const IndividualModal = () => { const [ips, setIps] = useState([""]) useEffect(() => { - setPhones(individual?.phone_numbers.map(({ phone_number }: any) => phone_number) || [""]) - setIps(individual?.ip_addresses.map(({ ip_address }: any) => ip_address) || [""]) + setPhones(individual?.phone_numbers?.map(({ phone_number }: any) => phone_number) || [""]) + setIps(individual?.ip_addresses?.map(({ ip_address }: any) => ip_address) || [""]) setAccounts(individual?.social_accounts || [""]) setImage(individual?.image_url) }, [individual]) @@ -85,279 +86,587 @@ const IndividualModal = () => { ) } + return ( - -
-
-
- User Profile + +
+
+ {individual?.full_name || "User Profile"} +
+ {/* */}
-
-
- - - {individual?.full_name?.[0] || "?"} - -
-
- - - Overview - - Social accounts - {isLoadingRelations ? ( - Loading... - ) : ( - 0 ? "default" : "outline"} className="ml-1"> - {accounts?.length} - +
+ +
+ +
+ {/* Left column - Profile image and basic info */} +
+
+ + + + {individual?.full_name?.[0] || "?"} + + +
+

{individual?.full_name}

+ {individual?.job_title &&

{individual.job_title}

} +
+ +
+ {individual?.email && ( +
+ + {individual.email} +
)} - - - Emails - {isLoadingEmails ? ( - Loading... - ) : ( - 0 ? "default" : "outline"} className="ml-1"> - {emails?.length} - + + {phones[0] && ( +
+ + {phones[0]} +
)} -
- - Phone numbers - {isLoadingRelations ? ( - Loading... - ) : ( - 0 ? "default" : "outline"} className="ml-1"> - {phones?.length} - + + {individual?.location && ( +
+ + {individual.location} +
)} -
- - IP addresses - {isLoadingRelations ? ( - Loading... - ) : ( - 0 ? "default" : "outline"} className="ml-1"> - {ips?.length} - + + {individual?.company && ( +
+ + {individual.company} +
)} -
- - Relations - {isLoadingRelations ? ( - Loading... - ) : ( - 0 ? "default" : "outline"} className="ml-1"> - {relations?.length} - +
+
+ +
+

Profile

+
+ {individual?.birth_date && ( +
+ Birthday + {individual.birth_date} +
)} - - -
- -
- {individual && - Object.keys(individual) - .filter((key) => typeof individual[key] !== "object" || !Array.isArray(individual[key])) - .map((key) => ( -
- - -
- ))} + + {individual?.family_status && ( +
+ Family status + {individual.family_status} +
+ )} + + {individual?.education && ( +
+ Education + {individual.education} +
+ )} + + {individual?.job_title && ( +
+ Job title + {individual.job_title} +
+ )} + + {individual?.company && ( +
+ Company + {individual.company} +
+ )} +
+
+ + {/* Score card */} +
+
+

Overall score

+
+
+ 60 +
+ + + + + + + + + +
- - -
- {emails.length === 0 && ( -

- No email registered. Click on edit to add one. -

- )} - {emails.map((email: any, index: number) => ( -
- 0 ? "destructive" : "default"}> - - {email.email}{" "} - {email.breaches.length === 0 - ? " is not yet involved in a data breach." - : ` was involved in ${email.breaches.length} data breach(es).`} - - - +
+ +
+
+
+ Phone score + 60 +
+ +
+ +
+
+ Email score + 10 +
+ +
+ +
+
+ IP address score + +
+ +
+
+
+
+ + {/* Right column - Tabs content */} +
+ + + + Overview + + + Social accounts + 0 ? "default" : "outline"} className="ml-1 bg-zinc-700"> + {accounts?.length || 0} + + + + Emails + 0 ? "default" : "outline"} className="ml-1 bg-zinc-700"> + {emails?.length || 0} + + + + Phone numbers + 0 ? "default" : "outline"} className="ml-1 bg-zinc-700"> + {phones?.length || 0} + + + + IP addresses + 0 ? "default" : "outline"} className="ml-1 bg-zinc-700"> + {ips?.length || 0} + + + + Relations + 0 ? "default" : "outline"} className="ml-1 bg-zinc-700"> + {relations?.length || 0} + + + + +
+ + {editMode ? ( +
+ {individual && + Object.keys(individual) + .filter((key) => typeof individual[key] !== "object" || !Array.isArray(individual[key])) + .map((key) => ( +
+ + +
+ ))}
- ))} -
- - -
- {accounts.length === 0 && ( -

- No account registered. Click on edit to add one. -

- )} - {accounts.map((account: any, index) => ( - - - - {/* @ts-ignore */} - - {account?.platform?.[0] || "?"} - -
-

{account?.platform}

-

- {account?.username || No username} -

- - {account?.profile_url} - + ) : ( +
+ {/* Job History */} +
+

Job History

+
+
+
+ +
+
+
+
+

Data Analyst

+

Vertex Dynamics

+
+ Nov 2023 - Mar 2024 +
+
+
+ +
+
+ +
+
+
+
+

Senior Software Engineer

+

Innovatech Solutions

+
+ Jul 2022 - Nov 2023 +
+
+
+ +
+
+ +
+
+
+
+

Software Engineer

+

Quantum Systems

+
+ Feb 2021 - Dec 2022 +
+
+
- - - ))} - {editMode && ( - +
+
)} -
- - -
- {phones.length === 0 && ( -

- No phone number registered. Click on edit to add one. -

- )} - {phones.map((phone, index) => ( -
- handleFieldChange(index, e.target.value, setPhones)} - placeholder="Phone Number" - type="tel" - disabled={!editMode} - className="flex-grow" - /> - {editMode && ( - + )} + + + ))} + + {editMode && ( + + )} +
+ + + +
+ {emails.length === 0 && ( +

+ No email registered. Click on edit to add one. +

+ )} + + {emails.map((email: any, index: number) => ( +
+ 0 ? "destructive" : "default"} + className={ + email.breaches.length > 0 + ? "bg-red-950/50 border-red-900 text-red-200" + : "bg-zinc-800 border-zinc-700" + } > - - - )} -
- ))} - {editMode && ( - - )} -
-
- -
- {ips.length === 0 && ( -

- No IP address registered. Click on edit to add one. -

- )} - {ips.map((ip, index) => ( -
- handleFieldChange(index, e.target.value, setIps)} - placeholder="IP address" - type="text" - disabled={!editMode} - className="flex-grow" - /> - {editMode && - ( + + + {email.email}{" "} + {email.breaches.length === 0 + ? " is not yet involved in a data breach." + : ` was involved in ${email.breaches.length} data breach(es).`} + + + +
+ ))} +
+
+ + +
+ {phones.length === 0 && ( +

+ No phone number registered. Click on edit to add one. +

+ )} + + {phones.map((phone, index) => ( +
+
+ + {editMode ? ( + handleFieldChange(index, e.target.value, setPhones)} + placeholder="Phone Number" + type="tel" + className="bg-transparent border-0 p-0 h-auto focus-visible:ring-0 focus-visible:ring-offset-0" + /> + ) : ( + {phone} + )} +
+ + {editMode && ( )} -
- ))} - {editMode && ( - - )} -
-
- -
- {relations.length === 0 && ( -

- No relation registered. Click on edit to add one. -

- )} - {relations.map((relation) => ( - setIndividualId(relation.id)} - > - - - - {relation.full_name?.[0] || "?"} - -
-

{relation.full_name}

-

{relation.relation_type}

+
+ ))} + + {editMode && ( + + )} +
+
+ + +
+ {ips.length === 0 && ( +

+ No IP address registered. Click on edit to add one. +

+ )} + + {ips.map((ip, index) => ( +
+
+ + + + + + {editMode ? ( + handleFieldChange(index, e.target.value, setIps)} + placeholder="IP address" + type="text" + className="bg-transparent border-0 p-0 h-auto focus-visible:ring-0 focus-visible:ring-offset-0" + /> + ) : ( + {ip} + )}
- - - ))} -
- -
- + + {editMode && ( + + )} +
+ ))} + + {editMode && ( + + )} +
+
+ + +
+ {relations.length === 0 && ( +

+ No relation registered. Click on edit to add one. +

+ )} + + {relations.map((relation) => ( + setIndividualId(relation.id)} + > + + + + + {relation.full_name?.[0] || "?"} + + +
+

{relation.full_name}

+

{relation.relation_type}

+
+
+
+ ))} +
+
+
+ +
-
+ {editMode && ( + + + + + )} +
- - - {editMode && } - - +
) } -export default IndividualModal \ No newline at end of file +export default IndividualModal diff --git a/flowsint-web/src/components/investigations/layout.tsx b/flowsint-web/src/components/investigations/layout.tsx index c5018e3..42b027f 100644 --- a/flowsint-web/src/components/investigations/layout.tsx +++ b/flowsint-web/src/components/investigations/layout.tsx @@ -32,7 +32,7 @@ const InvestigationLayout = ({ return ( <> {/* */} - {/* {panelOpen && + {/* {panelOpen &&
@@ -52,24 +52,25 @@ const InvestigationLayout = ({
} */} - {/* */} -
-
-
-
- / -
- -
-
- -
+ {/* */} +
+
+
+
+ /
- {children} +
- {/* +
+ +
+
+ {children} +
+ {/*
- */} + */} + ) } diff --git a/flowsint-web/src/components/investigations/new-actions.tsx b/flowsint-web/src/components/investigations/new-actions.tsx index c375707..572b10d 100644 --- a/flowsint-web/src/components/investigations/new-actions.tsx +++ b/flowsint-web/src/components/investigations/new-actions.tsx @@ -4,64 +4,184 @@ import { useState } from "react" import { useParams } from "next/navigation" import { supabase } from "@/lib/supabase/client" import { Button } from "@/components/ui/button" -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" -import { Avatar, AvatarFallback } from "@/components/ui/avatar" import { Input } from "@/components/ui/input" -import { Plus } from "lucide-react" +import { AlertCircle, AtSign, Camera, Facebook, Github, GithubIcon, Instagram, Locate, MapPin, MessageCircleDashed, Phone, PlusIcon, Send, User } from "lucide-react" import type React from "react" // Added import for React import { toast } from "sonner" +import { + DropdownMenu, + DropdownMenuItem, + DropdownMenuSubTrigger, + DropdownMenuTrigger, + DropdownMenuSub, + DropdownMenuPortal, + DropdownMenuContent, + DropdownMenuSubContent +} from "@/components/ui/dropdown-menu" +import { nodesTypes } from "@/lib/utils" +import { Alert, AlertTitle, AlertDescription } from "../ui/alert" +import { Badge } from "../ui/badge" +import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle } from "../ui/dialog" export default function NewActions({ addNodes }: { addNodes: any }) { const { investigation_id } = useParams() - const [open, setOpen] = useState(false) + const [openAddNodeModal, setOpenNodeModal] = useState(false) + const [currentNodeType, setCurrentNodeType] = useState(null) + const [error, setError] = useState(null) + const [loading, setLoading] = useState(false) - const onSubmit = async (e: React.FormEvent) => { - e.preventDefault() - const formData = new FormData(e.currentTarget) - const data = Object.fromEntries(formData) - try { - const { data: node, error } = await supabase - .from("individuals") - .insert({ ...data, investigation_id: investigation_id?.toString() }) - .select("*") - .single() - if (error) return toast.error("Could not create new individual:" + JSON.stringify(error)) - addNodes({ - id: node.id, - type: "individual", - data: { ...node, label: data.full_name }, - position: { x: -100, y: -100 }, - }) - toast.success("New individual created.") - setOpen(false) - } catch (error) { - toast.error("Could not create new individual:" + JSON.stringify(error)) + const handleOpenAddNodeModal = (e: { stopPropagation: () => void }, tableName: string, individualId?: string) => { + e.stopPropagation() + if (!nodesTypes[tableName as keyof typeof nodesTypes]) { + toast.error("Invalid node type.") + return } + setCurrentNodeType(nodesTypes[tableName as keyof typeof nodesTypes]) + setError(null) + setOpenNodeModal(true) } + const onSubmitNewNodeModal = async (e: { + preventDefault: () => void + currentTarget: HTMLFormElement | undefined + }) => { + e.preventDefault() + const data = Object.fromEntries(new FormData(e.currentTarget)) + await handleAddNode(data) + } + const handleAddNode = async (data: any) => { + try { + setLoading(true) + const dataToInsert = { ...data, investigation_id } + const { data: nodeData, error: insertError } = await supabase + .from(currentNodeType.table) + .insert(dataToInsert) + .select("*") + .single() + if (insertError) { + toast.error(insertError.details) + setLoading(false) + return + } + if (!nodeData) { + toast.error("Failed to create node.") + setLoading(false) + return + } + const newNode = { + id: nodeData.id, + type: currentNodeType.type, + data: { ...nodeData, label: data[currentNodeType.fields[0]] }, + position: { x: 0, y: 0 }, + } + addNodes(newNode) + setOpenNodeModal(false) + setError(null) + } catch (error) { + toast.error("An unexpected error occurred") + } + } return ( - - - {} - - -
- - A - -
- -
- + + handleOpenAddNodeModal(e, "individuals")}> + New relation + + handleOpenAddNodeModal(e, "phone_numbers")}> + + Phone number + + handleOpenAddNodeModal(e, "physical_addresses")}> + + Physical address + + handleOpenAddNodeModal(e, "emails")}> + + Email address + + handleOpenAddNodeModal(e, "ip_addresses")}> + + IP address + + + Social account + + + handleOpenAddNodeModal(e, "social_accounts_facebook")}> + + Facebook + + handleOpenAddNodeModal(e, "social_accounts_instagram")}> + + Instagram + + handleOpenAddNodeModal(e, "social_accounts_telegram")}> + + Telegram + + handleOpenAddNodeModal(e, "social_accounts_signal")}> + + Signal + + handleOpenAddNodeModal(e, "social_accounts_snapchat")}> + + Snapchat + + handleOpenAddNodeModal(e, "social_accounts_github")}> + + Github + + handleOpenAddNodeModal(e, "social_accounts_coco")}> + Coco{" "} + + soon + + + + + + + + + + New {currentNodeType?.type} + Add a new related {currentNodeType?.type}. + +
+ {currentNodeType?.fields.map((field: any, i: number) => { + const [key, value] = field.split(":") + return ( + + ) + })} +
+ {error && ( + + + Error + {error} + + )} +
+ + + +
-
- - - + + + + ) + } diff --git a/flowsint-web/src/components/investigations/scans-drawer/scan-button.tsx b/flowsint-web/src/components/investigations/scans-drawer/scan-button.tsx index 0ab700b..437f0c8 100644 --- a/flowsint-web/src/components/investigations/scans-drawer/scan-button.tsx +++ b/flowsint-web/src/components/investigations/scans-drawer/scan-button.tsx @@ -19,12 +19,7 @@ import { } from "@/components/ui/sheet" import { ScanTable } from "./scan-table" import { toast } from "sonner" - -// Create Supabase client -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || "" -const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "" -const supabase = createClient(supabaseUrl, supabaseKey) - +import { supabase } from "@/lib/supabase/client" export type Scan = { id: string value: string diff --git a/flowsint-web/src/components/investigations/sketch/graph.tsx b/flowsint-web/src/components/investigations/sketch/graph.tsx index 8879ecc..105482b 100644 --- a/flowsint-web/src/components/investigations/sketch/graph.tsx +++ b/flowsint-web/src/components/investigations/sketch/graph.tsx @@ -179,10 +179,10 @@ const FlowControls = memo( - {currentNode && ( + {/* {currentNode && ( - )} + )} */} ) }, diff --git a/flowsint-web/src/components/investigations/sketch/large-data-graph.tsx b/flowsint-web/src/components/investigations/sketch/large-data-graph.tsx index 794979e..ea8e08f 100644 --- a/flowsint-web/src/components/investigations/sketch/large-data-graph.tsx +++ b/flowsint-web/src/components/investigations/sketch/large-data-graph.tsx @@ -229,7 +229,7 @@ function LayoutFlow({ refetch, theme }: LayoutFlowProps) { }, []) return ( -
+
@@ -266,7 +266,6 @@ function LayoutFlow({ refetch, theme }: LayoutFlowProps) {
-
+
+ Environment + +
+ Log out diff --git a/flowsint-web/src/components/ui/dialog.tsx b/flowsint-web/src/components/ui/dialog.tsx index e40e4aa..14f8560 100644 --- a/flowsint-web/src/components/ui/dialog.tsx +++ b/flowsint-web/src/components/ui/dialog.tsx @@ -63,6 +63,7 @@ function DialogContent({ {...props} > {children} + Close diff --git a/flowsint-web/src/lib/actions/auth.ts b/flowsint-web/src/lib/actions/auth.ts index 55da990..f8d780f 100644 --- a/flowsint-web/src/lib/actions/auth.ts +++ b/flowsint-web/src/lib/actions/auth.ts @@ -32,9 +32,8 @@ export async function login(formData: FormData) { if (error) { redirect('/error') } - - revalidatePath('/', 'layout') - redirect('/') + // revalidatePath('/', 'layout') + // redirect('/') } export async function signup(formData: FormData) { diff --git a/flowsint-web/src/lib/actions/search.ts b/flowsint-web/src/lib/actions/search.ts index d95b5df..9f567fd 100644 --- a/flowsint-web/src/lib/actions/search.ts +++ b/flowsint-web/src/lib/actions/search.ts @@ -2,7 +2,7 @@ import { createClient } from "../supabase/server"; export async function checkEmail(email: string, investigation_id: string) { - const url = `http://localhost:5000/scan/`; + const url = `${process.env.NEXT_PUBLIC_DOCKER_FLOWSINT_API}/scan/`; const response = await fetch(url, { method: 'POST', headers: { diff --git a/flowsint-web/src/lib/supabase/middleware.ts b/flowsint-web/src/lib/supabase/middleware.ts index 208c07f..21a5df1 100644 --- a/flowsint-web/src/lib/supabase/middleware.ts +++ b/flowsint-web/src/lib/supabase/middleware.ts @@ -6,7 +6,7 @@ export async function updateSession(request: NextRequest) { request, }) const supabase = createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_DOCKER_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { diff --git a/flowsint-web/src/lib/supabase/server.ts b/flowsint-web/src/lib/supabase/server.ts index f90cd3a..cbbb6d7 100644 --- a/flowsint-web/src/lib/supabase/server.ts +++ b/flowsint-web/src/lib/supabase/server.ts @@ -6,7 +6,7 @@ export async function createClient() { const supabaseToken = cookieStore.get('sb-token')?.value return createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_DOCKER_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { auth: { diff --git a/flowsint-web/src/lib/utils.ts b/flowsint-web/src/lib/utils.ts index c4eb8a7..c2d4678 100644 --- a/flowsint-web/src/lib/utils.ts +++ b/flowsint-web/src/lib/utils.ts @@ -219,4 +219,42 @@ export const formatFileSize = (bytes: number) => { if (bytes < 1024) return bytes + " B" else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB" else return (bytes / 1048576).toFixed(1) + " MB" +} + +export const nodesTypes = { + emails: { table: "emails", type: "email", fields: ["email"] }, + individuals: { table: "individuals", type: "individual", fields: ["full_name"] }, + phone_numbers: { table: "phone_numbers", type: "phone", fields: ["phone_number"] }, + ip_addresses: { table: "ip_addresses", type: "ip", fields: ["ip_address"] }, + social_accounts_facebook: { + table: "social_accounts", + type: "social", + fields: ["profile_url", "username", "platform:facebook"], + }, + social_accounts_instagram: { + table: "social_accounts", + type: "social", + fields: ["profile_url", "username", "platform:instagram"], + }, + social_accounts_telegram: { + table: "social_accounts", + type: "social", + fields: ["profile_url", "username", "platform:telegram"], + }, + social_accounts_snapchat: { + table: "social_accounts", + type: "social", + fields: ["profile_url", "username", "platform:snapchat"], + }, + social_accounts_signal: { + table: "social_accounts", + type: "social", + fields: ["profile_url", "username", "platform:signal"], + }, + social_accounts_github: { + table: "social_accounts", + type: "social", + fields: ["profile_url", "username", "platform:github"], + }, + physical_addresses: { table: "physical_addresses", type: "address", fields: ["address", "city", "country", "zip"] }, } \ No newline at end of file diff --git a/flowsint-web/src/store/investigation-store.ts b/flowsint-web/src/store/investigation-store.ts index 1cfee9a..aad5756 100644 --- a/flowsint-web/src/store/investigation-store.ts +++ b/flowsint-web/src/store/investigation-store.ts @@ -1,6 +1,6 @@ "use client" -import createWithEqualityFn from "zustand" +import { create } from "zustand" import { persist, createJSONStorage } from "zustand/middleware" import { useQuery, type QueryObserverResult } from "@tanstack/react-query" import type { Investigation, Individual, Email, Phone, Social, IP, Relation, Address } from "@/types/investigation" @@ -64,7 +64,7 @@ interface InvestigationState { const isServer = typeof window === "undefined" -export const useInvestigationStore = createWithEqualityFn( +export const useInvestigationStore = create( persist( (set, get) => ({ // UI State diff --git a/package.json b/package.json index 333dc44..fc4b6bc 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,10 @@ "author": "Eliott Morcillo ", "license": "MIT", "scripts": { - "dev": "docker compose up -d" + "dev": "docker compose up -d", + "compose:start": "docker compose -f docker-compose.override.yml up -d" }, "devDependencies": { "supabase": "^2.19.7" } -} +} \ No newline at end of file diff --git a/proxy/docker-compose.yml b/proxy/docker-compose.yml new file mode 100644 index 0000000..fdd67ff --- /dev/null +++ b/proxy/docker-compose.yml @@ -0,0 +1,26 @@ +services: + reverse-proxy: + image: traefik:v3.1 + command: + - "--api.insecure=true" + - "--providers.docker" + - "--entrypoints.ws.address=:80" + # - "--entryPoints.websecure.address=:443" + # - "--entrypoints.websecure.http.tls.certresolver=myresolver" + # - "--providers.file.filename=/etc/traefik/dynamic.yml" + - "--providers.file.watch=true" + ports: + # The HTTP port + - "80:80" + # - "443:443" + # The Web UI (enabled by --api.insecure=true) + - "8080:8080" + restart: unless-stopped + volumes: + # - ./certs:/etc/traefik/certs + # - ./dynamic.yml:/etc/traefik/dynamic.yml + - /var/run/docker.sock:/var/run/docker.sock +networks: + default: + name: traefik + driver: bridge \ No newline at end of file diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 7a35881..0000000 --- a/yarn.lock +++ /dev/null @@ -1,421 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - -"@isaacs/fs-minipass@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" - integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== - dependencies: - minipass "^7.0.4" - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - -agent-base@^7.1.2: - version "7.1.3" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" - integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" - integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== - -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -bin-links@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-5.0.0.tgz#2b0605b62dd5e1ddab3b92a3c4e24221cae06cca" - integrity sha512-sdleLVfCjBtgO5cNjA2HVRvWBJAHs4zwenaCPMNJAJU0yNxpzj80IpjOIimkpkr+mhlA+how5poQtt53PygbHA== - dependencies: - cmd-shim "^7.0.0" - npm-normalize-package-bin "^4.0.0" - proc-log "^5.0.0" - read-cmd-shim "^5.0.0" - write-file-atomic "^6.0.0" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -chownr@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" - integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== - -cmd-shim@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-7.0.0.tgz#23bcbf69fff52172f7e7c02374e18fb215826d95" - integrity sha512-rtpaCbr164TPPh+zFdkWpCyZuKkjpAzODfaZCf/SVJZzJN+4bHQb/LP3Jzq5/+84um3XXY8r548XiWKSborwVw== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -data-uri-to-buffer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" - integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== - -debug@4: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== - dependencies: - ms "^2.1.3" - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -fetch-blob@^3.1.2, fetch-blob@^3.1.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" - integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== - dependencies: - node-domexception "^1.0.0" - web-streams-polyfill "^3.0.3" - -foreground-child@^3.1.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" - integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== - dependencies: - cross-spawn "^7.0.6" - signal-exit "^4.0.1" - -formdata-polyfill@^4.0.10: - version "4.0.10" - resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" - integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== - dependencies: - fetch-blob "^3.1.2" - -glob@^10.3.7: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - -https-proxy-agent@^7.0.2: - version "7.0.6" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" - integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== - dependencies: - agent-base "^7.1.2" - debug "4" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4, minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - -minizlib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" - integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== - dependencies: - minipass "^7.0.4" - rimraf "^5.0.5" - -mkdirp@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" - integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -node-domexception@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - -node-fetch@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" - integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== - dependencies: - data-uri-to-buffer "^4.0.0" - fetch-blob "^3.1.4" - formdata-polyfill "^4.0.10" - -npm-normalize-package-bin@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz#df79e70cd0a113b77c02d1fe243c96b8e618acb1" - integrity sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w== - -package-json-from-dist@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" - integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-scurry@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -proc-log@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8" - integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ== - -read-cmd-shim@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-5.0.0.tgz#6e5450492187a0749f6c80dcbef0debc1117acca" - integrity sha512-SEbJV7tohp3DAAILbEMPXavBjAnMN0tVnh4+9G8ihV4Pq3HYF9h8QNez9zkJ1ILkv9G2BjdzwctznGZXgu/HGw== - -rimraf@^5.0.5: - version "5.0.10" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" - integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== - dependencies: - glob "^10.3.7" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -supabase@^2.19.7: - version "2.19.7" - resolved "https://registry.yarnpkg.com/supabase/-/supabase-2.19.7.tgz#7a2b754fb548616745eca88ec9334a84514fa76d" - integrity sha512-+Qu5xHvk/CnAHb5iDpCWCX0SdwFR5smpTG6VUSTw/T8WxCDDQsIb2mui2pu18wqKOZ0NLs/ZTDDb2+yPejTRBQ== - dependencies: - bin-links "^5.0.0" - https-proxy-agent "^7.0.2" - node-fetch "^3.3.2" - tar "7.4.3" - -tar@7.4.3: - version "7.4.3" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" - integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== - dependencies: - "@isaacs/fs-minipass" "^4.0.0" - chownr "^3.0.0" - minipass "^7.1.2" - minizlib "^3.0.1" - mkdirp "^3.0.1" - yallist "^5.0.0" - -web-streams-polyfill@^3.0.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" - integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -write-file-atomic@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-6.0.0.tgz#e9c89c8191b3ef0606bc79fb92681aa1aa16fa93" - integrity sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^4.0.1" - -yallist@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" - integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==