@realtimex/email-automator 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/.env.example +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +247 -0
  4. package/api/server.ts +130 -0
  5. package/api/src/config/index.ts +102 -0
  6. package/api/src/middleware/auth.ts +166 -0
  7. package/api/src/middleware/errorHandler.ts +97 -0
  8. package/api/src/middleware/index.ts +4 -0
  9. package/api/src/middleware/rateLimit.ts +87 -0
  10. package/api/src/middleware/validation.ts +118 -0
  11. package/api/src/routes/actions.ts +214 -0
  12. package/api/src/routes/auth.ts +157 -0
  13. package/api/src/routes/emails.ts +144 -0
  14. package/api/src/routes/health.ts +36 -0
  15. package/api/src/routes/index.ts +22 -0
  16. package/api/src/routes/migrate.ts +76 -0
  17. package/api/src/routes/rules.ts +149 -0
  18. package/api/src/routes/settings.ts +229 -0
  19. package/api/src/routes/sync.ts +152 -0
  20. package/api/src/services/eventLogger.ts +52 -0
  21. package/api/src/services/gmail.ts +456 -0
  22. package/api/src/services/intelligence.ts +288 -0
  23. package/api/src/services/microsoft.ts +368 -0
  24. package/api/src/services/processor.ts +596 -0
  25. package/api/src/services/scheduler.ts +255 -0
  26. package/api/src/services/supabase.ts +144 -0
  27. package/api/src/utils/contentCleaner.ts +114 -0
  28. package/api/src/utils/crypto.ts +80 -0
  29. package/api/src/utils/logger.ts +142 -0
  30. package/bin/email-automator-deploy.js +79 -0
  31. package/bin/email-automator-setup.js +144 -0
  32. package/bin/email-automator.js +61 -0
  33. package/dist/assets/index-BQ1uMdFh.js +97 -0
  34. package/dist/assets/index-Dzi17fx5.css +1 -0
  35. package/dist/email-automator-logo.svg +51 -0
  36. package/dist/favicon.svg +45 -0
  37. package/dist/index.html +14 -0
  38. package/index.html +13 -0
  39. package/package.json +112 -0
  40. package/public/email-automator-logo.svg +51 -0
  41. package/public/favicon.svg +45 -0
  42. package/scripts/deploy-functions.sh +55 -0
  43. package/scripts/migrate.sh +177 -0
  44. package/src/App.tsx +622 -0
  45. package/src/components/AccountSettings.tsx +310 -0
  46. package/src/components/AccountSettingsPage.tsx +390 -0
  47. package/src/components/Configuration.tsx +1345 -0
  48. package/src/components/Dashboard.tsx +940 -0
  49. package/src/components/ErrorBoundary.tsx +71 -0
  50. package/src/components/LiveTerminal.tsx +308 -0
  51. package/src/components/LoadingSpinner.tsx +39 -0
  52. package/src/components/Login.tsx +371 -0
  53. package/src/components/Logo.tsx +57 -0
  54. package/src/components/SetupWizard.tsx +388 -0
  55. package/src/components/Toast.tsx +109 -0
  56. package/src/components/migration/MigrationBanner.tsx +97 -0
  57. package/src/components/migration/MigrationModal.tsx +458 -0
  58. package/src/components/migration/MigrationPulseIndicator.tsx +38 -0
  59. package/src/components/mode-toggle.tsx +24 -0
  60. package/src/components/theme-provider.tsx +72 -0
  61. package/src/components/ui/alert.tsx +66 -0
  62. package/src/components/ui/button.tsx +57 -0
  63. package/src/components/ui/card.tsx +75 -0
  64. package/src/components/ui/dialog.tsx +133 -0
  65. package/src/components/ui/input.tsx +22 -0
  66. package/src/components/ui/label.tsx +24 -0
  67. package/src/components/ui/otp-input.tsx +184 -0
  68. package/src/context/AppContext.tsx +422 -0
  69. package/src/context/MigrationContext.tsx +53 -0
  70. package/src/context/TerminalContext.tsx +31 -0
  71. package/src/core/actions.ts +76 -0
  72. package/src/core/auth.ts +108 -0
  73. package/src/core/intelligence.ts +76 -0
  74. package/src/core/processor.ts +112 -0
  75. package/src/hooks/useRealtimeEmails.ts +111 -0
  76. package/src/index.css +140 -0
  77. package/src/lib/api-config.ts +42 -0
  78. package/src/lib/api-old.ts +228 -0
  79. package/src/lib/api.ts +421 -0
  80. package/src/lib/migration-check.ts +264 -0
  81. package/src/lib/sounds.ts +120 -0
  82. package/src/lib/supabase-config.ts +117 -0
  83. package/src/lib/supabase.ts +28 -0
  84. package/src/lib/types.ts +166 -0
  85. package/src/lib/utils.ts +6 -0
  86. package/src/main.tsx +10 -0
  87. package/supabase/.env.example +15 -0
  88. package/supabase/.temp/cli-latest +1 -0
  89. package/supabase/.temp/gotrue-version +1 -0
  90. package/supabase/.temp/pooler-url +1 -0
  91. package/supabase/.temp/postgres-version +1 -0
  92. package/supabase/.temp/project-ref +1 -0
  93. package/supabase/.temp/rest-version +1 -0
  94. package/supabase/.temp/storage-migration +1 -0
  95. package/supabase/.temp/storage-version +1 -0
  96. package/supabase/config.toml +95 -0
  97. package/supabase/functions/_shared/auth-helper.ts +76 -0
  98. package/supabase/functions/_shared/auth.ts +33 -0
  99. package/supabase/functions/_shared/cors.ts +45 -0
  100. package/supabase/functions/_shared/encryption.ts +70 -0
  101. package/supabase/functions/_shared/supabaseAdmin.ts +14 -0
  102. package/supabase/functions/api-v1-accounts/index.ts +133 -0
  103. package/supabase/functions/api-v1-emails/index.ts +177 -0
  104. package/supabase/functions/api-v1-rules/index.ts +177 -0
  105. package/supabase/functions/api-v1-settings/index.ts +247 -0
  106. package/supabase/functions/auth-gmail/index.ts +197 -0
  107. package/supabase/functions/auth-microsoft/index.ts +215 -0
  108. package/supabase/functions/setup/index.ts +92 -0
  109. package/supabase/migrations/20260114000000_initial_schema.sql +81 -0
  110. package/supabase/migrations/20260115000000_add_user_settings.sql +49 -0
  111. package/supabase/migrations/20260115000001_add_auth_flow.sql +80 -0
  112. package/supabase/migrations/20260115000002_fix_permissions.sql +5 -0
  113. package/supabase/migrations/20260115000003_fix_init_state_permissions.sql +9 -0
  114. package/supabase/migrations/20260115000004_add_migration_rpc.sql +13 -0
  115. package/supabase/migrations/20260115000005_add_provider_creds.sql +7 -0
  116. package/supabase/migrations/20260115000006_backfill_profiles.sql +22 -0
  117. package/supabase/migrations/20260116000000_add_sync_scope.sql +15 -0
  118. package/supabase/migrations/20260116000001_per_account_sync_scope.sql +19 -0
  119. package/supabase/migrations/20260116000002_add_llm_api_key.sql +5 -0
  120. package/supabase/migrations/20260117000000_refactor_integrations.sql +36 -0
  121. package/supabase/migrations/20260117000001_add_processing_events.sql +30 -0
  122. package/supabase/migrations/20260117000002_multi_actions.sql +15 -0
  123. package/supabase/migrations/20260117000003_seed_default_rules.sql +77 -0
  124. package/supabase/migrations/20260117000004_rule_instructions.sql +5 -0
  125. package/supabase/migrations/20260117000005_rule_attachments.sql +7 -0
  126. package/supabase/migrations/20260117000006_setup_storage.sql +32 -0
  127. package/supabase/migrations/20260117000007_add_system_logs.sql +26 -0
  128. package/supabase/migrations/20260117000008_link_logs_to_accounts.sql +8 -0
  129. package/supabase/migrations/20260117000009_convert_toggles_to_rules.sql +28 -0
  130. package/supabase/migrations/20260117000010_add_atomic_action_append.sql +13 -0
  131. package/supabase/migrations/20260117000011_add_profile_avatar.sql +4 -0
  132. package/supabase/migrations/20260117000012_setup_avatars_storage.sql +26 -0
  133. package/supabase/templates/confirmation.html +76 -0
  134. package/supabase/templates/email-change.html +76 -0
  135. package/supabase/templates/invite.html +72 -0
  136. package/supabase/templates/magic-link.html +68 -0
  137. package/supabase/templates/recovery.html +82 -0
  138. package/tsconfig.json +36 -0
  139. package/vite.config.ts +162 -0
@@ -0,0 +1,80 @@
1
+ -- Create profiles table
2
+ create table public.profiles (
3
+ id uuid references auth.users not null primary key,
4
+ first_name text,
5
+ last_name text,
6
+ email text,
7
+ is_admin boolean default false,
8
+ created_at timestamptz default now(),
9
+ updated_at timestamptz default now()
10
+ );
11
+
12
+ -- RLS
13
+ alter table public.profiles enable row level security;
14
+
15
+ create policy "Users can view their own profile" on public.profiles
16
+ for select using (auth.uid() = id);
17
+
18
+ create policy "Users can update their own profile" on public.profiles
19
+ for update using (auth.uid() = id);
20
+
21
+ -- Trigger to handle new user creation
22
+ create or replace function public.handle_new_user()
23
+ returns trigger
24
+ language plpgsql
25
+ security definer set search_path = ''
26
+ as $$
27
+ declare
28
+ profile_count int;
29
+ begin
30
+ select count(id) into profile_count
31
+ from public.profiles;
32
+
33
+ insert into public.profiles (id, first_name, last_name, email, is_admin)
34
+ values (
35
+ new.id,
36
+ new.raw_user_meta_data ->> 'first_name',
37
+ new.raw_user_meta_data ->> 'last_name',
38
+ new.email,
39
+ -- If it's the first user, make them admin. Otherwise, standard user.
40
+ case when profile_count > 0 then false else true end
41
+ );
42
+ return new;
43
+ end;
44
+ $$;
45
+
46
+ -- Trigger to handle user updates
47
+ create or replace function public.handle_update_user()
48
+ returns trigger
49
+ language plpgsql
50
+ security definer set search_path = ''
51
+ as $$
52
+ begin
53
+ update public.profiles
54
+ set
55
+ first_name = new.raw_user_meta_data ->> 'first_name',
56
+ last_name = new.raw_user_meta_data ->> 'last_name',
57
+ email = new.email,
58
+ updated_at = now()
59
+ where id = new.id;
60
+
61
+ return new;
62
+ end;
63
+ $$;
64
+
65
+ -- Register triggers
66
+ create trigger on_auth_user_created
67
+ after insert on auth.users
68
+ for each row execute procedure public.handle_new_user();
69
+
70
+ create trigger on_auth_user_updated
71
+ after update on auth.users
72
+ for each row execute procedure public.handle_update_user();
73
+
74
+ -- Create init_state view for frontend check
75
+ create or replace view init_state
76
+ with (security_invoker=off)
77
+ as
78
+ select count(id) as is_initialized
79
+ from public.profiles
80
+ limit 1;
@@ -0,0 +1,5 @@
1
+ -- Grant usage on schema public to anon and authenticated
2
+ grant usage on schema public to anon, authenticated;
3
+
4
+ -- Grant select on init_state view to anon and authenticated
5
+ grant select on public.init_state to anon, authenticated;
@@ -0,0 +1,9 @@
1
+ -- Grant permissions for init_state view
2
+ grant select on public.init_state to anon, authenticated;
3
+
4
+ -- Grant permissions for profiles table (needed for some flows although view is security defined)
5
+ -- Good practice to ensure basic read capability if needed later
6
+ grant select on public.profiles to anon, authenticated;
7
+
8
+ -- Ensure triggers can run (usually default, but good to ensure)
9
+ grant usage on schema public to anon, authenticated;
@@ -0,0 +1,13 @@
1
+ -- Function to get the latest migration timestamp
2
+ -- Used by the frontend to check if the app is out of sync with the database
3
+ create or replace function public.get_latest_migration_timestamp()
4
+ returns text
5
+ language sql
6
+ security definer
7
+ set search_path = ''
8
+ as $$
9
+ select max(version) from supabase_migrations.schema_migrations;
10
+ $$;
11
+
12
+ -- Grant execute permission to anon and authenticated users
13
+ grant execute on function public.get_latest_migration_timestamp() to anon, authenticated;
@@ -0,0 +1,7 @@
1
+ -- Add provider credentials to user_settings table
2
+ ALTER TABLE user_settings
3
+ ADD COLUMN IF NOT EXISTS google_client_id TEXT,
4
+ ADD COLUMN IF NOT EXISTS google_client_secret TEXT,
5
+ ADD COLUMN IF NOT EXISTS microsoft_client_id TEXT,
6
+ ADD COLUMN IF NOT EXISTS microsoft_client_secret TEXT,
7
+ ADD COLUMN IF NOT EXISTS microsoft_tenant_id TEXT;
@@ -0,0 +1,22 @@
1
+ -- Backfill profiles for any auth.users that don't have a profile entry
2
+ -- This handles cases where:
3
+ -- 1. Users were created before the trigger was added
4
+ -- 2. The trigger didn't fire (e.g., admin API in some Supabase configs)
5
+
6
+ -- Insert profiles for users who don't have one
7
+ insert into public.profiles (id, email, is_admin, created_at, updated_at)
8
+ select
9
+ u.id,
10
+ u.email,
11
+ -- First user (oldest) becomes admin if no admin exists
12
+ case when not exists (select 1 from public.profiles where is_admin = true)
13
+ and u.created_at = (select min(created_at) from auth.users)
14
+ then true
15
+ else false
16
+ end,
17
+ u.created_at,
18
+ now()
19
+ from auth.users u
20
+ where not exists (
21
+ select 1 from public.profiles p where p.id = u.id
22
+ );
@@ -0,0 +1,15 @@
1
+ -- Migration to add sync scope and checkpointing columns
2
+
3
+ -- 1. Add columns to user_settings
4
+ ALTER TABLE user_settings
5
+ ADD COLUMN IF NOT EXISTS sync_start_date TIMESTAMP WITH TIME ZONE,
6
+ ADD COLUMN IF NOT EXISTS sync_max_emails_per_run INTEGER DEFAULT 50;
7
+
8
+ -- 2. Add column to email_accounts
9
+ ALTER TABLE email_accounts
10
+ ADD COLUMN IF NOT EXISTS last_sync_checkpoint TEXT;
11
+
12
+ -- 3. Comment on new columns
13
+ COMMENT ON COLUMN user_settings.sync_start_date IS 'Starting date for synchronization. Emails before this date will be ignored.';
14
+ COMMENT ON COLUMN user_settings.sync_max_emails_per_run IS 'Maximum number of emails to process in a single synchronization run.';
15
+ COMMENT ON COLUMN email_accounts.last_sync_checkpoint IS 'Stores the last processed timestamp (internalDate for Gmail, receivedDateTime for Outlook) for incremental sync.';
@@ -0,0 +1,19 @@
1
+ -- Migration to move sync settings to email_accounts (per-account scope)
2
+
3
+ -- 1. Add columns to email_accounts
4
+ ALTER TABLE email_accounts
5
+ ADD COLUMN IF NOT EXISTS sync_start_date TIMESTAMP WITH TIME ZONE,
6
+ ADD COLUMN IF NOT EXISTS sync_max_emails_per_run INTEGER DEFAULT 50,
7
+ ADD COLUMN IF NOT EXISTS last_sync_at TIMESTAMP WITH TIME ZONE,
8
+ ADD COLUMN IF NOT EXISTS last_sync_status TEXT DEFAULT 'idle',
9
+ ADD COLUMN IF NOT EXISTS last_sync_error TEXT;
10
+
11
+ -- 2. Clean up user_settings (optional but recommended for clarity)
12
+ ALTER TABLE user_settings
13
+ DROP COLUMN IF EXISTS sync_start_date,
14
+ DROP COLUMN IF EXISTS sync_max_emails_per_run;
15
+
16
+ -- 3. Comments
17
+ COMMENT ON COLUMN email_accounts.sync_start_date IS 'Only fetch emails received after this date for this specific account.';
18
+ COMMENT ON COLUMN email_accounts.sync_max_emails_per_run IS 'Maximum emails to process per sync run for this specific account.';
19
+ COMMENT ON COLUMN email_accounts.last_sync_status IS 'Status of the last sync: idle, syncing, success, error.';
@@ -0,0 +1,5 @@
1
+ -- Migration to add LLM API Key to user settings
2
+ ALTER TABLE user_settings ADD COLUMN IF NOT EXISTS llm_api_key TEXT;
3
+
4
+ -- Update comment
5
+ COMMENT ON COLUMN user_settings.llm_api_key IS 'API Key for the configured LLM provider (encrypted or plain text depending on security policy)';
@@ -0,0 +1,36 @@
1
+ -- Create integrations table for storing provider credentials
2
+ CREATE TABLE IF NOT EXISTS integrations (
3
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4
+ user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
5
+ provider TEXT NOT NULL,
6
+ credentials JSONB NOT NULL DEFAULT '{}',
7
+ is_enabled BOOLEAN DEFAULT true,
8
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
9
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
10
+ UNIQUE(user_id, provider)
11
+ );
12
+
13
+ -- Enable RLS
14
+ ALTER TABLE integrations ENABLE ROW LEVEL SECURITY;
15
+
16
+ -- RLS Policy
17
+ CREATE POLICY "Users can only access their own integrations" ON integrations
18
+ FOR ALL USING (auth.uid() = user_id);
19
+
20
+ -- Add updated_at trigger for integrations
21
+ CREATE TRIGGER update_integrations_updated_at
22
+ BEFORE UPDATE ON integrations
23
+ FOR EACH ROW
24
+ EXECUTE FUNCTION update_updated_at_column();
25
+
26
+ -- Refactor user_settings to remove plain text credentials
27
+ ALTER TABLE user_settings
28
+ DROP COLUMN IF EXISTS google_client_id,
29
+ DROP COLUMN IF EXISTS google_client_secret,
30
+ DROP COLUMN IF EXISTS microsoft_client_id,
31
+ DROP COLUMN IF EXISTS microsoft_client_secret,
32
+ DROP COLUMN IF EXISTS microsoft_tenant_id;
33
+
34
+ -- Add preferences column for future extensibility (optional but good practice)
35
+ ALTER TABLE user_settings
36
+ ADD COLUMN IF NOT EXISTS preferences JSONB DEFAULT '{}';
@@ -0,0 +1,30 @@
1
+ -- Create processing_events table for granular logging
2
+ CREATE TABLE IF NOT EXISTS processing_events (
3
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4
+ run_id UUID REFERENCES processing_logs(id) ON DELETE CASCADE,
5
+ email_id UUID REFERENCES emails(id) ON DELETE SET NULL,
6
+ event_type TEXT NOT NULL CHECK (event_type IN ('info', 'analysis', 'action', 'error')),
7
+ agent_state TEXT, -- e.g., 'Fetching', 'Analyzing', 'Deciding', 'Acting'
8
+ details JSONB, -- Stores LLM inputs/outputs, reasoning, confidence
9
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
10
+ );
11
+
12
+ -- Enable RLS
13
+ ALTER TABLE processing_events ENABLE ROW LEVEL SECURITY;
14
+
15
+ -- RLS Policy: Users can access events if they own the parent run log
16
+ CREATE POLICY "Users can access their own processing events" ON processing_events
17
+ FOR ALL USING (EXISTS (
18
+ SELECT 1 FROM processing_logs
19
+ WHERE processing_logs.id = processing_events.run_id
20
+ AND processing_logs.user_id = auth.uid()
21
+ ));
22
+
23
+ -- Enable Realtime for this table (standard Supabase publication)
24
+ DO $$
25
+ BEGIN
26
+ IF NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname = 'supabase_realtime' AND schemaname = 'public' AND tablename = 'processing_events') THEN
27
+ ALTER PUBLICATION supabase_realtime ADD TABLE processing_events;
28
+ END IF;
29
+ END
30
+ $$;
@@ -0,0 +1,15 @@
1
+ -- Enable multiple actions per email
2
+ ALTER TABLE emails ADD COLUMN IF NOT EXISTS suggested_actions TEXT[] DEFAULT '{}';
3
+ ALTER TABLE emails ADD COLUMN IF NOT EXISTS actions_taken TEXT[] DEFAULT '{}';
4
+
5
+ -- Migrate existing data (preserve history)
6
+ UPDATE emails
7
+ SET suggested_actions = ARRAY[suggested_action]
8
+ WHERE suggested_action IS NOT NULL AND suggested_action != 'none';
9
+
10
+ UPDATE emails
11
+ SET actions_taken = ARRAY[action_taken]
12
+ WHERE action_taken IS NOT NULL AND action_taken != 'none';
13
+
14
+ -- We will keep the old columns for now to avoid breaking legacy code immediately,
15
+ -- but the application logic will switch to using the array columns.
@@ -0,0 +1,77 @@
1
+ -- Update rules action check constraint to support new actions
2
+ ALTER TABLE rules DROP CONSTRAINT IF EXISTS rules_action_check;
3
+ ALTER TABLE rules ADD CONSTRAINT rules_action_check
4
+ CHECK (action IN ('delete', 'archive', 'draft', 'star', 'read'));
5
+
6
+ -- Seed default rules for existing users
7
+ DO $$
8
+ DECLARE
9
+ user_rec RECORD;
10
+ BEGIN
11
+ FOR user_rec IN SELECT id FROM auth.users LOOP
12
+ -- Archive Newsletters
13
+ IF NOT EXISTS (SELECT 1 FROM rules WHERE user_id = user_rec.id AND name = 'Archive Newsletters') THEN
14
+ INSERT INTO rules (user_id, name, condition, action, is_enabled)
15
+ VALUES (user_rec.id, 'Archive Newsletters', '{"category": "newsletter"}', 'archive', true);
16
+ END IF;
17
+
18
+ -- Archive Receipts
19
+ IF NOT EXISTS (SELECT 1 FROM rules WHERE user_id = user_rec.id AND name = 'Archive Receipts') THEN
20
+ INSERT INTO rules (user_id, name, condition, action, is_enabled)
21
+ VALUES (user_rec.id, 'Archive Receipts', '{"category": "transactional"}', 'archive', true);
22
+ END IF;
23
+
24
+ -- Trash Promotions (Disabled by default)
25
+ IF NOT EXISTS (SELECT 1 FROM rules WHERE user_id = user_rec.id AND name = 'Trash Promotions') THEN
26
+ INSERT INTO rules (user_id, name, condition, action, is_enabled)
27
+ VALUES (user_rec.id, 'Trash Promotions', '{"category": "promotional"}', 'delete', false);
28
+ END IF;
29
+
30
+ -- Flag Important
31
+ IF NOT EXISTS (SELECT 1 FROM rules WHERE user_id = user_rec.id AND name = 'Flag Important') THEN
32
+ INSERT INTO rules (user_id, name, condition, action, is_enabled)
33
+ VALUES (user_rec.id, 'Flag Important', '{"priority": "High"}', 'star', true);
34
+ END IF;
35
+
36
+ -- Auto-Trash Old Newsletters (30 days)
37
+ IF NOT EXISTS (SELECT 1 FROM rules WHERE user_id = user_rec.id AND name = 'Auto-Trash Old Newsletters') THEN
38
+ INSERT INTO rules (user_id, name, condition, action, is_enabled)
39
+ VALUES (user_rec.id, 'Auto-Trash Old Newsletters', '{"category": "newsletter", "older_than_days": 30}', 'delete', true);
40
+ END IF;
41
+ END LOOP;
42
+ END $$;
43
+
44
+ -- Update trigger for new users to include default rules
45
+ CREATE OR REPLACE FUNCTION public.handle_new_user()
46
+ RETURNS TRIGGER
47
+ LANGUAGE plpgsql
48
+ SECURITY DEFINER SET search_path = ''
49
+ AS $$
50
+ DECLARE
51
+ profile_count int;
52
+ BEGIN
53
+ SELECT count(id) INTO profile_count
54
+ FROM public.profiles;
55
+
56
+ -- Create Profile
57
+ INSERT INTO public.profiles (id, first_name, last_name, email, is_admin)
58
+ VALUES (
59
+ new.id,
60
+ new.raw_user_meta_data ->> 'first_name',
61
+ new.raw_user_meta_data ->> 'last_name',
62
+ new.email,
63
+ CASE WHEN profile_count > 0 THEN false ELSE true END
64
+ );
65
+
66
+ -- Insert Default Rules
67
+ INSERT INTO public.rules (user_id, name, condition, action, is_enabled)
68
+ VALUES
69
+ (new.id, 'Archive Newsletters', '{"category": "newsletter"}', 'archive', true),
70
+ (new.id, 'Archive Receipts', '{"category": "transactional"}', 'archive', true),
71
+ (new.id, 'Trash Promotions', '{"category": "promotional"}', 'delete', false),
72
+ (new.id, 'Flag Important', '{"priority": "High"}', 'star', true),
73
+ (new.id, 'Auto-Trash Old Newsletters', '{"category": "newsletter", "older_than_days": 30}', 'delete', true);
74
+
75
+ RETURN new;
76
+ END;
77
+ $$;
@@ -0,0 +1,5 @@
1
+ -- Add instructions to rules for contextual drafting
2
+ ALTER TABLE rules ADD COLUMN IF NOT EXISTS instructions TEXT;
3
+
4
+ -- Update the check constraint to ensure draft rules can have instructions (optional check)
5
+ COMMENT ON COLUMN rules.instructions IS 'Specific instructions for the AI when generating a draft reply for this rule.';
@@ -0,0 +1,7 @@
1
+ -- Add attachments support to rules
2
+ ALTER TABLE rules ADD COLUMN IF NOT EXISTS attachments JSONB DEFAULT '[]';
3
+
4
+ -- Comment for clarity
5
+ COMMENT ON COLUMN rules.attachments IS 'Array of attachment objects: [{ name: string, path: string, type: string, size: number }]';
6
+
7
+ -- Update types/constraints if necessary (JSONB is flexible for conditions)
@@ -0,0 +1,32 @@
1
+ -- Create rule-attachments bucket
2
+ INSERT INTO storage.buckets (id, name, public)
3
+ VALUES ('rule-attachments', 'rule-attachments', false)
4
+ ON CONFLICT (id) DO NOTHING;
5
+
6
+ -- RLS Policies for rule-attachments bucket
7
+ -- Allow users to upload their own attachments
8
+ CREATE POLICY "Allow users to upload rule attachments"
9
+ ON storage.objects FOR INSERT
10
+ TO authenticated
11
+ WITH CHECK (
12
+ bucket_id = 'rule-attachments' AND
13
+ (storage.foldername(name))[1] = auth.uid()::text
14
+ );
15
+
16
+ -- Allow users to view their own attachments
17
+ CREATE POLICY "Allow users to view their own rule attachments"
18
+ ON storage.objects FOR SELECT
19
+ TO authenticated
20
+ USING (
21
+ bucket_id = 'rule-attachments' AND
22
+ (storage.foldername(name))[1] = auth.uid()::text
23
+ );
24
+
25
+ -- Allow users to delete their own attachments
26
+ CREATE POLICY "Allow users to delete their own rule attachments"
27
+ ON storage.objects FOR DELETE
28
+ TO authenticated
29
+ USING (
30
+ bucket_id = 'rule-attachments' AND
31
+ (storage.foldername(name))[1] = auth.uid()::text
32
+ );
@@ -0,0 +1,26 @@
1
+ -- Create system_logs table for technical logging
2
+ CREATE TABLE IF NOT EXISTS system_logs (
3
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4
+ user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
5
+ level TEXT NOT NULL CHECK (level IN ('debug', 'info', 'warn', 'error')),
6
+ source TEXT NOT NULL, -- e.g., 'API', 'Processor', 'GmailService'
7
+ message TEXT NOT NULL,
8
+ metadata JSONB DEFAULT '{}',
9
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
10
+ );
11
+
12
+ -- Enable RLS
13
+ ALTER TABLE system_logs ENABLE ROW LEVEL SECURITY;
14
+
15
+ -- RLS Policy: Users can only see logs they generated (if associated with a user)
16
+ CREATE POLICY "Users can view their own system logs" ON system_logs
17
+ FOR SELECT USING (auth.uid() = user_id OR user_id IS NULL);
18
+
19
+ -- Enable Realtime
20
+ DO $$
21
+ BEGIN
22
+ IF NOT EXISTS (SELECT 1 FROM pg_publication_tables WHERE pubname = 'supabase_realtime' AND schemaname = 'public' AND tablename = 'system_logs') THEN
23
+ ALTER PUBLICATION supabase_realtime ADD TABLE system_logs;
24
+ END IF;
25
+ END
26
+ $$;
@@ -0,0 +1,8 @@
1
+ -- Link processing logs to specific accounts for better attribution
2
+ ALTER TABLE processing_logs ADD COLUMN IF NOT EXISTS account_id UUID REFERENCES email_accounts(id) ON DELETE CASCADE;
3
+
4
+ -- Add index for better performance when joining or filtering by account
5
+ CREATE INDEX IF NOT EXISTS idx_processing_logs_account_id ON processing_logs(account_id);
6
+
7
+ -- Comment for clarity
8
+ COMMENT ON COLUMN processing_logs.account_id IS 'Reference to the email account being synced in this run.';
@@ -0,0 +1,28 @@
1
+ -- Migration to move global toggles into the rules table
2
+ -- This makes the system more consistent and fixes persistence issues.
3
+
4
+ -- 1. Create 'Auto-Trash Spam' rule for users who had it enabled
5
+ INSERT INTO rules (user_id, name, condition, action, is_enabled)
6
+ SELECT
7
+ user_id,
8
+ 'Auto-Trash Spam',
9
+ '{"category": "spam", "is_useless": true}'::jsonb,
10
+ 'delete',
11
+ COALESCE(auto_trash_spam, false)
12
+ FROM user_settings
13
+ ON CONFLICT DO NOTHING;
14
+
15
+ -- 2. Create 'Smart Drafts' rule for users who had it enabled
16
+ INSERT INTO rules (user_id, name, condition, action, is_enabled)
17
+ SELECT
18
+ user_id,
19
+ 'Smart Drafts',
20
+ '{"suggested_actions": ["reply"]}'::jsonb,
21
+ 'draft',
22
+ COALESCE(smart_drafts, false)
23
+ FROM user_settings
24
+ ON CONFLICT DO NOTHING;
25
+
26
+ -- 3. Clean up the user_settings table
27
+ ALTER TABLE user_settings DROP COLUMN IF EXISTS auto_trash_spam;
28
+ ALTER TABLE user_settings DROP COLUMN IF EXISTS smart_drafts;
@@ -0,0 +1,13 @@
1
+ -- Create atomic function to append actions to an email record without duplicates
2
+ CREATE OR REPLACE FUNCTION append_email_action(p_email_id UUID, p_action TEXT)
3
+ RETURNS void
4
+ LANGUAGE plpgsql
5
+ SECURITY DEFINER
6
+ AS $$
7
+ BEGIN
8
+ UPDATE emails
9
+ SET actions_taken = array_append(COALESCE(actions_taken, '{}'), p_action)
10
+ WHERE id = p_email_id
11
+ AND NOT (COALESCE(actions_taken, '{}') @> ARRAY[p_action]);
12
+ END;
13
+ $$;
@@ -0,0 +1,4 @@
1
+ -- Add avatar_url to profiles table
2
+ ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS avatar_url TEXT;
3
+
4
+ -- Ensure RLS allows users to update their own avatar_url (it already does by allowing all updates to own profile)
@@ -0,0 +1,26 @@
1
+ -- Setup avatars storage bucket
2
+ INSERT INTO storage.buckets (id, name, public)
3
+ VALUES ('avatars', 'avatars', true)
4
+ ON CONFLICT (id) DO NOTHING;
5
+
6
+ -- RLS Policies for avatars
7
+ CREATE POLICY "Public profiles are viewable by everyone" ON storage.objects
8
+ FOR SELECT USING (bucket_id = 'avatars');
9
+
10
+ CREATE POLICY "Users can upload their own avatar" ON storage.objects
11
+ FOR INSERT WITH CHECK (
12
+ bucket_id = 'avatars' AND
13
+ (storage.foldername(name))[1] = auth.uid()::text
14
+ );
15
+
16
+ CREATE POLICY "Users can update their own avatar" ON storage.objects
17
+ FOR UPDATE USING (
18
+ bucket_id = 'avatars' AND
19
+ (storage.foldername(name))[1] = auth.uid()::text
20
+ );
21
+
22
+ CREATE POLICY "Users can delete their own avatar" ON storage.objects
23
+ FOR DELETE USING (
24
+ bucket_id = 'avatars' AND
25
+ (storage.foldername(name))[1] = auth.uid()::text
26
+ );
@@ -0,0 +1,76 @@
1
+ <!doctype html>
2
+ <html>
3
+
4
+ <head>
5
+ <style>
6
+ body {
7
+ font-family: Arial, sans-serif;
8
+ background-color: #f4f4f4;
9
+ margin: 0;
10
+ padding: 0;
11
+ }
12
+
13
+ .container {
14
+ width: 100%;
15
+ max-width: 600px;
16
+ margin: 0 auto;
17
+ background-color: #ffffff;
18
+ padding: 20px;
19
+ border-radius: 8px;
20
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
21
+ }
22
+
23
+ .header {
24
+ text-align: center;
25
+ padding: 10px 0;
26
+ }
27
+
28
+ .header h2 {
29
+ margin: 0;
30
+ color: #333333;
31
+ }
32
+
33
+ .content {
34
+ padding: 20px;
35
+ line-height: 1.6;
36
+ color: #555555;
37
+ }
38
+
39
+ .content p {
40
+ margin: 0 0 10px;
41
+ }
42
+
43
+ .button {
44
+ display: block;
45
+ width: 200px;
46
+ margin: 20px auto;
47
+ padding: 10px;
48
+ text-align: center;
49
+ background-color: #007bff;
50
+ color: #ffffff !important;
51
+ text-decoration: none;
52
+ border-radius: 5px;
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <div class="container">
59
+ <div class="header">
60
+ <h2>Confirm your email</h2>
61
+ </div>
62
+ <div class="content">
63
+ <p>Hello,</p>
64
+ <p>
65
+ Thanks for signing up for Email Automator! Please confirm your email
66
+ address by clicking the button below.
67
+ </p>
68
+ <p>
69
+ <a href="{{ .ConfirmationURL }}auth-callback.html" class="button">Confirm my email</a>
70
+ </p>
71
+ <p>The Email Automator team</p>
72
+ </div>
73
+ </div>
74
+ </body>
75
+
76
+ </html>
@@ -0,0 +1,76 @@
1
+ <!doctype html>
2
+ <html>
3
+
4
+ <head>
5
+ <style>
6
+ body {
7
+ font-family: Arial, sans-serif;
8
+ background-color: #f4f4f4;
9
+ margin: 0;
10
+ padding: 0;
11
+ }
12
+
13
+ .container {
14
+ width: 100%;
15
+ max-width: 600px;
16
+ margin: 0 auto;
17
+ background-color: #ffffff;
18
+ padding: 20px;
19
+ border-radius: 8px;
20
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
21
+ }
22
+
23
+ .header {
24
+ text-align: center;
25
+ padding: 10px 0;
26
+ }
27
+
28
+ .header h2 {
29
+ margin: 0;
30
+ color: #333333;
31
+ }
32
+
33
+ .content {
34
+ padding: 20px;
35
+ line-height: 1.6;
36
+ color: #555555;
37
+ }
38
+
39
+ .content p {
40
+ margin: 0 0 10px;
41
+ }
42
+
43
+ .button {
44
+ display: block;
45
+ width: 200px;
46
+ margin: 20px auto;
47
+ padding: 10px;
48
+ text-align: center;
49
+ background-color: #007bff;
50
+ color: #ffffff !important;
51
+ text-decoration: none;
52
+ border-radius: 5px;
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <div class="container">
59
+ <div class="header">
60
+ <h2>Confirm your new email address</h2>
61
+ </div>
62
+ <div class="content">
63
+ <p>Hello,</p>
64
+ <p>
65
+ You've requested to change your email address for Email Automator.
66
+ Please click the button below to confirm this change.
67
+ </p>
68
+ <p>
69
+ <a href="{{ .ConfirmationURL }}auth-callback.html" class="button">Confirm new email</a>
70
+ </p>
71
+ <p>The Email Automator team</p>
72
+ </div>
73
+ </div>
74
+ </body>
75
+
76
+ </html>