diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..f996e373
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,42 @@
+# Eryxon Flow - Environment Configuration
+# Copy this file to .env and fill in your values
+
+# =============================================================================
+# SUPABASE CONFIGURATION (Required)
+# =============================================================================
+# Get these from your Supabase project dashboard: Settings -> API
+
+# Your Supabase project URL
+VITE_SUPABASE_URL="https://your-project-id.supabase.co"
+
+# Supabase anon/public key (safe to expose in frontend)
+VITE_SUPABASE_PUBLISHABLE_KEY="your-anon-key-here"
+
+# Supabase project ID (the part before .supabase.co)
+VITE_SUPABASE_PROJECT_ID="your-project-id"
+
+# =============================================================================
+# OPTIONAL CONFIGURATION
+# =============================================================================
+
+# App title (shown in browser tab)
+# VITE_APP_TITLE="Eryxon Flow"
+
+# Default language (en, nl, de)
+# VITE_DEFAULT_LANGUAGE="en"
+
+# =============================================================================
+# SELF-HOSTED NOTES
+# =============================================================================
+#
+# For self-hosted deployments:
+# 1. Create a Supabase project (cloud or self-hosted)
+# 2. Apply the database schema from supabase/migrations/
+# 3. Deploy edge functions: supabase functions deploy
+# 4. Configure storage buckets: parts-images, issues
+# 5. Set these environment variables
+#
+# See docs/SELF_HOSTING_GUIDE.md for complete instructions.
+#
+# License: BSL 1.1 - Self-hosting is free and unlimited.
+# You cannot offer commercial hosted versions that compete with eryxon.eu
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..053fba24
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,116 @@
+Business Source License 1.1
+
+License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
+"Business Source License" is a trademark of MariaDB Corporation Ab.
+
+-----------------------------------------------------------------------------
+
+Parameters
+
+Licensor: Sheet Metal Connect e.U.
+Licensed Work: Eryxon Flow
+ The Licensed Work is (c) 2025 Sheet Metal Connect e.U.
+Additional Use Grant: You may make use of the Licensed Work, provided that
+ you do not use the Licensed Work for a Production Use
+ that is Competing with any offering of the Licensor.
+
+ A Production Use is Competing if it offers to third
+ parties access to the Licensed Work in a manner that
+ competes with or substitutes for the Licensor's own
+ commercial hosting offering of the Licensed Work.
+
+ For clarity, the following uses are expressly permitted:
+
+ 1. Self-hosting the Licensed Work for your own internal
+ business operations, regardless of organization size
+
+ 2. Using the Licensed Work for development, testing,
+ evaluation, and educational purposes
+
+ 3. Offering consulting, integration, or customization
+ services built around the Licensed Work
+
+ 4. Creating derivative works for your own internal use
+
+Change Date: Four years from the date the Licensed Work is published
+
+Change License: Apache License, Version 2.0
+
+-----------------------------------------------------------------------------
+
+Terms
+
+The Licensor hereby grants you the right to copy, modify, create derivative
+works, redistribute, and make non-production use of the Licensed Work. The
+Licensor may make an Additional Use Grant, above, permitting limited
+production use.
+
+Effective on the Change Date, or the fourth anniversary of the first publicly
+available distribution of a specific version of the Licensed Work under this
+License, whichever comes first, the Licensor hereby grants you rights under
+the terms of the Change License, and the rights granted in the paragraph
+above terminate.
+
+If your use of the Licensed Work does not comply with the requirements
+currently in effect as described in this License, you must purchase a
+commercial license from the Licensor, its affiliated entities, or authorized
+resellers, or you must refrain from using the Licensed Work.
+
+All copies of the original and modified Licensed Work, and derivative works
+of the Licensed Work, are subject to this License. This License applies
+separately for each version of the Licensed Work and the Change Date may vary
+for each version of the Licensed Work released by Licensor.
+
+You must conspicuously display this License on each original or modified copy
+of the Licensed Work. If you receive the Licensed Work in original or
+modified form from a third party, the terms and conditions set forth in this
+License apply to your use of that work.
+
+Any use of the Licensed Work in violation of this License will automatically
+terminate your rights under this License for the current and all other
+versions of the Licensed Work.
+
+This License does not grant you any right in any trademark or logo of
+Licensor or its affiliates (provided that you may use a trademark or logo of
+Licensor as expressly required by this License).
+
+TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
+AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
+EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
+TITLE.
+
+MariaDB hereby grants you permission to use this License's text to license
+your works, and to refer to it using the trademark "Business Source License",
+as long as you comply with the Covenants of Licensor below.
+
+-----------------------------------------------------------------------------
+
+Covenants of Licensor
+
+In consideration of the right to use this License's text and the "Business
+Source License" name and trademark, Licensor covenants to MariaDB, and to all
+other recipients of the licensed work to be provided by Licensor:
+
+1. To specify as the Change License the GPL Version 2.0 or any later version,
+ or a license that is compatible with GPL Version 2.0 or a later version,
+ where "compatible" means that software provided under the Change License can
+ be included in a program with software provided under GPL Version 2.0 or a
+ later version. Licensor may specify additional Change Licenses without
+ limitation.
+
+2. To either: (a) specify an additional grant of rights to use that does not
+ impose any additional restriction on the right granted in this License, as
+ the Additional Use Grant; or (b) insert the text "None".
+
+3. To specify a Change Date.
+
+4. Not to modify this License in any other way.
+
+-----------------------------------------------------------------------------
+
+Notice
+
+The Business Source License (this document, or the "License") is not an Open
+Source license. However, the Licensed Work will eventually be made available
+under an Open Source License, as stated in this License.
diff --git a/README.md b/README.md
index 32ec971a..4c7de92e 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,27 @@
-# Eryxon Flow - Manufacturing Execution System
+# Eryxon MES
-**Internal Project - Proprietary**
+**The simple, elegant and powerful manufacturing execution system that your people will love to use. Made for SMB metal fabrication.**
-
-
-
-
+## What Makes This Different
-
+ Download it, run it yourself, you're on your own. Source code available under BSL 1.1.
+ Unlimited jobs, parts, storage. Full API, webhooks, MCP server.
+ Community support via docs and GitHub Discussions only. See the{" "}
+
+ GitHub repository
+ .
@@ -202,29 +222,29 @@ Thank you!`;
-
Self-Service Only
+
Self-Service
- No onboarding calls. No consultants. No phone support. Sign up, configure your stages
- and materials, connect your API, and go. Documentation and email support only.
+ No onboarding calls. No consultants. Sign up, configure your stages
+ and materials, connect your API, and go. Documentation and email support (Pro+) only.
- Monitor your usage, track jobs and parts count, and manage your subscription from your{' '}
+ Monitor your usage from your{' '}
My Plan page.
- You'll see real-time usage statistics and receive alerts when approaching tier limits.
+ You'll see real-time statistics and alerts when approaching limits.
diff --git a/src/pages/common/MyPlan.tsx b/src/pages/common/MyPlan.tsx
index 375837cc..329f552b 100644
--- a/src/pages/common/MyPlan.tsx
+++ b/src/pages/common/MyPlan.tsx
@@ -28,15 +28,15 @@ const pricingTiers = [
id: "free",
name: "Free",
price: "€0",
- description: "Perfect for small shops getting started",
+ description: "Try it. Very limited.",
features: [
- "100 jobs per month",
- "1,000 parts per month",
- "5 GB file storage",
+ "25 jobs per month",
+ "250 parts per month",
+ "500 MB storage",
"Limited API access",
- "Multi-tenant architecture",
- "Community support (docs only)",
- "Basic workflow tracking",
+ "No webhooks",
+ "No MCP server",
+ "Docs only",
],
},
{
@@ -44,36 +44,45 @@ const pricingTiers = [
name: "Pro",
price: "€97",
popular: true,
- description: "For growing shops with higher volume",
+ description: "Real usage, my infra, email support.",
features: [
- "Unlimited users",
- "1,000 jobs per month",
- "10,000 parts per month",
- "50 GB file storage",
+ "500 jobs per month",
+ "5,000 parts per month",
+ "10 GB storage",
"Full API access",
- "Multi-tenant architecture",
- "Priority email support",
- "Advanced analytics",
- "Webhook integrations",
+ "Webhooks included",
+ "MCP server access",
+ "Email support",
],
},
{
id: "premium",
- name: "Enterprise",
+ name: "Premium",
price: "€497",
- description: "Custom, bespoke solution",
+ description: "High limits, SSO, priority support. Still my infra.",
features: [
- "Everything in Pro",
- "Unlimited jobs & parts",
- "Unlimited storage",
- "Unlimited usage",
- "Self-hosted (on-premises)",
- "Single-tenant architecture",
+ "Fair use (high limits)",
+ "100 GB storage",
+ "Full API access",
+ "Webhooks + MCP server",
"SSO/SAML authentication",
- "Dedicated support channel",
+ "White-label (optional)",
+ "Priority email support",
+ ],
+ },
+ {
+ id: "enterprise",
+ name: "Enterprise",
+ price: "Contact",
+ description: "Your network, I deploy, custom scope.",
+ features: [
+ "Unlimited jobs & parts",
+ "Your infrastructure",
+ "Your Supabase instance",
+ "SSO/SAML included",
+ "White-label included",
+ "Dedicated support",
"Custom SLA",
- "White-label options",
- "Advanced security controls",
],
},
];
diff --git a/supabase/functions/_shared/plan-limits.ts b/supabase/functions/_shared/plan-limits.ts
index 64382323..a24086b6 100644
--- a/supabase/functions/_shared/plan-limits.ts
+++ b/supabase/functions/_shared/plan-limits.ts
@@ -4,16 +4,18 @@
* This module provides utilities for enforcing subscription plan limits
* across all API endpoints.
*
- * Plan Limits:
- * - Free: 100 jobs, 1000 parts/month, 5GB storage, limited API access
- * - Pro: 1000 jobs, 10000 parts/month, 50GB storage, full API access
- * - Enterprise: Unlimited everything (premium tier in DB)
+ * Plan Limits (BSL 1.1 Model - 4 hosted tiers + self-hosted):
+ * - Free: 25 jobs/mo, 250 parts/mo, 1GB storage, limited API (no webhooks, no MCP)
+ * - Pro: 500 jobs/mo, 5000 parts/mo, 10GB storage, full API + webhooks + MCP
+ * - Premium: 2000 jobs/mo, 20000 parts/mo, 100GB storage, SSO/SAML, priority support
+ * - Enterprise: Unlimited, their infrastructure, custom scope
+ * - Self-hosted: Unlimited (configured via env, not enforced)
*/
import { SupabaseClient } from "https://esm.sh/@supabase/supabase-js@2";
export interface PlanLimits {
- plan: 'free' | 'pro' | 'premium';
+ plan: 'free' | 'pro' | 'premium' | 'enterprise';
max_jobs: number | null;
max_parts_per_month: number | null;
max_storage_gb: number | null;
@@ -148,16 +150,16 @@ export async function canCreateParts(
/**
* Check if a tenant has API access
* Free plan has limited API access (rate limited more aggressively)
- * Pro and Enterprise have full API access
+ * Pro, Premium, and Enterprise have full API access
*/
-export function getApiAccessLevel(plan: 'free' | 'pro' | 'premium'): 'limited' | 'full' {
+export function getApiAccessLevel(plan: 'free' | 'pro' | 'premium' | 'enterprise'): 'limited' | 'full' {
return plan === 'free' ? 'limited' : 'full';
}
/**
* Get rate limit configuration based on plan
*/
-export function getRateLimitConfig(plan: 'free' | 'pro' | 'premium'): {
+export function getRateLimitConfig(plan: 'free' | 'pro' | 'premium' | 'enterprise'): {
maxRequests: number;
windowMs: number;
} {
@@ -174,7 +176,12 @@ export function getRateLimitConfig(plan: 'free' | 'pro' | 'premium'): {
};
case 'premium':
return {
- maxRequests: 10000, // 10000 requests per hour
+ maxRequests: 5000, // 5000 requests per hour (priority)
+ windowMs: 60 * 60 * 1000,
+ };
+ case 'enterprise':
+ return {
+ maxRequests: 10000, // 10000 requests per hour (dedicated)
windowMs: 60 * 60 * 1000,
};
default:
@@ -274,13 +281,15 @@ export async function canUploadFile(
/**
* Get a user-friendly plan name
*/
-export function getPlanDisplayName(plan: 'free' | 'pro' | 'premium'): string {
+export function getPlanDisplayName(plan: 'free' | 'pro' | 'premium' | 'enterprise'): string {
switch (plan) {
case 'free':
return 'Free';
case 'pro':
return 'Pro';
case 'premium':
+ return 'Premium';
+ case 'enterprise':
return 'Enterprise';
default:
return 'Unknown';
diff --git a/supabase/migrations/20251201000000_update_plan_limits_bsl.sql b/supabase/migrations/20251201000000_update_plan_limits_bsl.sql
new file mode 100644
index 00000000..f52abb08
--- /dev/null
+++ b/supabase/migrations/20251201000000_update_plan_limits_bsl.sql
@@ -0,0 +1,143 @@
+-- Update plan limits for BSL 1.1 pricing model (4 hosted tiers + self-hosted)
+-- Free: 25 jobs/mo, 250 parts/mo, 500MB storage
+-- Pro: 500 jobs/mo, 5000 parts/mo, 10GB storage
+-- Premium: Fair use (2000 jobs/mo, 20000 parts/mo, 100GB storage)
+-- Enterprise: Unlimited (NULL) - their infrastructure
+
+-- Add 'enterprise' to the subscription_plan enum if not exists
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_enum
+ WHERE enumlabel = 'enterprise'
+ AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'subscription_plan')
+ ) THEN
+ ALTER TYPE subscription_plan ADD VALUE 'enterprise';
+ END IF;
+END $$;
+
+-- Update existing tenants by plan
+UPDATE public.tenants
+SET
+ max_jobs = 25,
+ max_parts_per_month = 250,
+ max_storage_gb = 1 -- 500MB rounded to 1GB for simplicity
+WHERE plan = 'free';
+
+UPDATE public.tenants
+SET
+ max_jobs = 500,
+ max_parts_per_month = 5000,
+ max_storage_gb = 10
+WHERE plan = 'pro';
+
+-- Premium now has fair use limits (high but not unlimited)
+UPDATE public.tenants
+SET
+ max_jobs = 2000,
+ max_parts_per_month = 20000,
+ max_storage_gb = 100
+WHERE plan = 'premium';
+
+-- Enterprise has unlimited (NULL)
+-- Note: Existing 'premium' tenants that should be enterprise need manual migration
+UPDATE public.tenants
+SET
+ max_jobs = NULL,
+ max_parts_per_month = NULL,
+ max_storage_gb = NULL
+WHERE plan = 'enterprise';
+
+-- Update the handle_new_tenant function with new free tier defaults
+CREATE OR REPLACE FUNCTION public.handle_new_tenant()
+RETURNS TRIGGER
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path = public
+AS $$
+BEGIN
+ -- Check if tenant exists
+ IF NOT EXISTS (SELECT 1 FROM public.tenants WHERE id = NEW.tenant_id) THEN
+ -- Create new tenant with free plan defaults (BSL 1.1 model)
+ INSERT INTO public.tenants (
+ id,
+ name,
+ plan,
+ status,
+ max_jobs,
+ max_parts_per_month,
+ max_storage_gb,
+ contact_email
+ ) VALUES (
+ NEW.tenant_id,
+ COALESCE(NEW.full_name || '''s Organization', 'New Organization'),
+ 'free',
+ 'active',
+ 25, -- Free tier: 25 jobs/mo
+ 250, -- Free tier: 250 parts/mo
+ 1, -- Free tier: ~500MB (1GB for simplicity)
+ NEW.email
+ );
+ END IF;
+
+ RETURN NEW;
+END;
+$$;
+
+-- Create or replace function to get plan limits by plan type
+-- Used for documentation and plan upgrades
+CREATE OR REPLACE FUNCTION public.get_plan_limits(p_plan subscription_plan)
+RETURNS TABLE (
+ plan_name TEXT,
+ max_jobs INTEGER,
+ max_parts_per_month INTEGER,
+ max_storage_gb INTEGER,
+ has_webhooks BOOLEAN,
+ has_mcp_server BOOLEAN,
+ has_sso BOOLEAN
+)
+LANGUAGE SQL
+STABLE
+AS $$
+ SELECT
+ p_plan::TEXT,
+ CASE p_plan
+ WHEN 'free' THEN 25
+ WHEN 'pro' THEN 500
+ WHEN 'premium' THEN 2000 -- Fair use
+ WHEN 'enterprise' THEN NULL -- Unlimited
+ END,
+ CASE p_plan
+ WHEN 'free' THEN 250
+ WHEN 'pro' THEN 5000
+ WHEN 'premium' THEN 20000 -- Fair use
+ WHEN 'enterprise' THEN NULL -- Unlimited
+ END,
+ CASE p_plan
+ WHEN 'free' THEN 1
+ WHEN 'pro' THEN 10
+ WHEN 'premium' THEN 100
+ WHEN 'enterprise' THEN NULL -- Unlimited
+ END,
+ CASE p_plan
+ WHEN 'free' THEN FALSE
+ WHEN 'pro' THEN TRUE
+ WHEN 'premium' THEN TRUE
+ WHEN 'enterprise' THEN TRUE
+ END,
+ CASE p_plan
+ WHEN 'free' THEN FALSE
+ WHEN 'pro' THEN TRUE
+ WHEN 'premium' THEN TRUE
+ WHEN 'enterprise' THEN TRUE
+ END,
+ CASE p_plan
+ WHEN 'free' THEN FALSE
+ WHEN 'pro' THEN FALSE
+ WHEN 'premium' THEN TRUE
+ WHEN 'enterprise' THEN TRUE
+ END;
+$$;
+
+-- Add comment for documentation
+COMMENT ON FUNCTION public.get_plan_limits IS 'Returns plan limits for BSL 1.1 pricing model. Free: 25/250/1GB. Pro: 500/5K/10GB. Premium: 2K/20K/100GB (fair use). Enterprise: unlimited.';