@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.
- package/.env.example +35 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/api/server.ts +130 -0
- package/api/src/config/index.ts +102 -0
- package/api/src/middleware/auth.ts +166 -0
- package/api/src/middleware/errorHandler.ts +97 -0
- package/api/src/middleware/index.ts +4 -0
- package/api/src/middleware/rateLimit.ts +87 -0
- package/api/src/middleware/validation.ts +118 -0
- package/api/src/routes/actions.ts +214 -0
- package/api/src/routes/auth.ts +157 -0
- package/api/src/routes/emails.ts +144 -0
- package/api/src/routes/health.ts +36 -0
- package/api/src/routes/index.ts +22 -0
- package/api/src/routes/migrate.ts +76 -0
- package/api/src/routes/rules.ts +149 -0
- package/api/src/routes/settings.ts +229 -0
- package/api/src/routes/sync.ts +152 -0
- package/api/src/services/eventLogger.ts +52 -0
- package/api/src/services/gmail.ts +456 -0
- package/api/src/services/intelligence.ts +288 -0
- package/api/src/services/microsoft.ts +368 -0
- package/api/src/services/processor.ts +596 -0
- package/api/src/services/scheduler.ts +255 -0
- package/api/src/services/supabase.ts +144 -0
- package/api/src/utils/contentCleaner.ts +114 -0
- package/api/src/utils/crypto.ts +80 -0
- package/api/src/utils/logger.ts +142 -0
- package/bin/email-automator-deploy.js +79 -0
- package/bin/email-automator-setup.js +144 -0
- package/bin/email-automator.js +61 -0
- package/dist/assets/index-BQ1uMdFh.js +97 -0
- package/dist/assets/index-Dzi17fx5.css +1 -0
- package/dist/email-automator-logo.svg +51 -0
- package/dist/favicon.svg +45 -0
- package/dist/index.html +14 -0
- package/index.html +13 -0
- package/package.json +112 -0
- package/public/email-automator-logo.svg +51 -0
- package/public/favicon.svg +45 -0
- package/scripts/deploy-functions.sh +55 -0
- package/scripts/migrate.sh +177 -0
- package/src/App.tsx +622 -0
- package/src/components/AccountSettings.tsx +310 -0
- package/src/components/AccountSettingsPage.tsx +390 -0
- package/src/components/Configuration.tsx +1345 -0
- package/src/components/Dashboard.tsx +940 -0
- package/src/components/ErrorBoundary.tsx +71 -0
- package/src/components/LiveTerminal.tsx +308 -0
- package/src/components/LoadingSpinner.tsx +39 -0
- package/src/components/Login.tsx +371 -0
- package/src/components/Logo.tsx +57 -0
- package/src/components/SetupWizard.tsx +388 -0
- package/src/components/Toast.tsx +109 -0
- package/src/components/migration/MigrationBanner.tsx +97 -0
- package/src/components/migration/MigrationModal.tsx +458 -0
- package/src/components/migration/MigrationPulseIndicator.tsx +38 -0
- package/src/components/mode-toggle.tsx +24 -0
- package/src/components/theme-provider.tsx +72 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/button.tsx +57 -0
- package/src/components/ui/card.tsx +75 -0
- package/src/components/ui/dialog.tsx +133 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/otp-input.tsx +184 -0
- package/src/context/AppContext.tsx +422 -0
- package/src/context/MigrationContext.tsx +53 -0
- package/src/context/TerminalContext.tsx +31 -0
- package/src/core/actions.ts +76 -0
- package/src/core/auth.ts +108 -0
- package/src/core/intelligence.ts +76 -0
- package/src/core/processor.ts +112 -0
- package/src/hooks/useRealtimeEmails.ts +111 -0
- package/src/index.css +140 -0
- package/src/lib/api-config.ts +42 -0
- package/src/lib/api-old.ts +228 -0
- package/src/lib/api.ts +421 -0
- package/src/lib/migration-check.ts +264 -0
- package/src/lib/sounds.ts +120 -0
- package/src/lib/supabase-config.ts +117 -0
- package/src/lib/supabase.ts +28 -0
- package/src/lib/types.ts +166 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +10 -0
- package/supabase/.env.example +15 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/gotrue-version +1 -0
- package/supabase/.temp/pooler-url +1 -0
- package/supabase/.temp/postgres-version +1 -0
- package/supabase/.temp/project-ref +1 -0
- package/supabase/.temp/rest-version +1 -0
- package/supabase/.temp/storage-migration +1 -0
- package/supabase/.temp/storage-version +1 -0
- package/supabase/config.toml +95 -0
- package/supabase/functions/_shared/auth-helper.ts +76 -0
- package/supabase/functions/_shared/auth.ts +33 -0
- package/supabase/functions/_shared/cors.ts +45 -0
- package/supabase/functions/_shared/encryption.ts +70 -0
- package/supabase/functions/_shared/supabaseAdmin.ts +14 -0
- package/supabase/functions/api-v1-accounts/index.ts +133 -0
- package/supabase/functions/api-v1-emails/index.ts +177 -0
- package/supabase/functions/api-v1-rules/index.ts +177 -0
- package/supabase/functions/api-v1-settings/index.ts +247 -0
- package/supabase/functions/auth-gmail/index.ts +197 -0
- package/supabase/functions/auth-microsoft/index.ts +215 -0
- package/supabase/functions/setup/index.ts +92 -0
- package/supabase/migrations/20260114000000_initial_schema.sql +81 -0
- package/supabase/migrations/20260115000000_add_user_settings.sql +49 -0
- package/supabase/migrations/20260115000001_add_auth_flow.sql +80 -0
- package/supabase/migrations/20260115000002_fix_permissions.sql +5 -0
- package/supabase/migrations/20260115000003_fix_init_state_permissions.sql +9 -0
- package/supabase/migrations/20260115000004_add_migration_rpc.sql +13 -0
- package/supabase/migrations/20260115000005_add_provider_creds.sql +7 -0
- package/supabase/migrations/20260115000006_backfill_profiles.sql +22 -0
- package/supabase/migrations/20260116000000_add_sync_scope.sql +15 -0
- package/supabase/migrations/20260116000001_per_account_sync_scope.sql +19 -0
- package/supabase/migrations/20260116000002_add_llm_api_key.sql +5 -0
- package/supabase/migrations/20260117000000_refactor_integrations.sql +36 -0
- package/supabase/migrations/20260117000001_add_processing_events.sql +30 -0
- package/supabase/migrations/20260117000002_multi_actions.sql +15 -0
- package/supabase/migrations/20260117000003_seed_default_rules.sql +77 -0
- package/supabase/migrations/20260117000004_rule_instructions.sql +5 -0
- package/supabase/migrations/20260117000005_rule_attachments.sql +7 -0
- package/supabase/migrations/20260117000006_setup_storage.sql +32 -0
- package/supabase/migrations/20260117000007_add_system_logs.sql +26 -0
- package/supabase/migrations/20260117000008_link_logs_to_accounts.sql +8 -0
- package/supabase/migrations/20260117000009_convert_toggles_to_rules.sql +28 -0
- package/supabase/migrations/20260117000010_add_atomic_action_append.sql +13 -0
- package/supabase/migrations/20260117000011_add_profile_avatar.sql +4 -0
- package/supabase/migrations/20260117000012_setup_avatars_storage.sql +26 -0
- package/supabase/templates/confirmation.html +76 -0
- package/supabase/templates/email-change.html +76 -0
- package/supabase/templates/invite.html +72 -0
- package/supabase/templates/magic-link.html +68 -0
- package/supabase/templates/recovery.html +82 -0
- package/tsconfig.json +36 -0
- 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,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,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>
|