-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathdocuments-migration.sql
More file actions
183 lines (154 loc) · 7.61 KB
/
Copy pathdocuments-migration.sql
File metadata and controls
183 lines (154 loc) · 7.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
-- =============================================================================
-- FutureTracker Documents Feature Migration
-- Phase 1.3: Resume/Portfolio Attachment
-- =============================================================================
-- Documents table (user's document library)
-- Stores metadata for uploaded files and external links
CREATE TABLE IF NOT EXISTS documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE NOT NULL,
name TEXT NOT NULL,
type TEXT CHECK (type IN ('resume', 'cover_letter', 'portfolio', 'other')) NOT NULL,
file_url TEXT, -- Supabase Storage URL or external link
file_size INTEGER, -- Size in bytes (for uploaded files)
storage_path TEXT, -- Supabase Storage path (for deletion)
version TEXT DEFAULT 'v1', -- User-defined version label
ats_score INTEGER, -- Rule-based ATS score from client-side analysis
ats_analyzed_at TIMESTAMPTZ, -- When client-side ATS analysis was last saved
ats_analysis JSONB, -- Raw client-side ATS analysis metadata
notes TEXT,
is_external BOOLEAN DEFAULT FALSE, -- True for portfolio links, false for uploads
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Enable RLS on documents table
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
-- Users can only view their own documents
CREATE POLICY "Users can view own documents" ON documents
FOR SELECT USING (
user_id IN (SELECT id FROM users WHERE clerk_id = current_setting('request.jwt.claims', true)::json->>'sub')
);
-- Users can create their own documents
CREATE POLICY "Users can create own documents" ON documents
FOR INSERT WITH CHECK (
user_id IN (SELECT id FROM users WHERE clerk_id = current_setting('request.jwt.claims', true)::json->>'sub')
);
-- Users can update their own documents
CREATE POLICY "Users can update own documents" ON documents
FOR UPDATE USING (
user_id IN (SELECT id FROM users WHERE clerk_id = current_setting('request.jwt.claims', true)::json->>'sub')
);
-- Users can delete their own documents
CREATE POLICY "Users can delete own documents" ON documents
FOR DELETE USING (
user_id IN (SELECT id FROM users WHERE clerk_id = current_setting('request.jwt.claims', true)::json->>'sub')
);
-- =============================================================================
-- Junction table: which document was used for which opportunity
-- =============================================================================
CREATE TABLE IF NOT EXISTS opportunity_documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
opportunity_id UUID REFERENCES opportunities(id) ON DELETE CASCADE NOT NULL,
document_id UUID REFERENCES documents(id) ON DELETE CASCADE NOT NULL,
submitted_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(opportunity_id, document_id)
);
-- Enable RLS on opportunity_documents table
ALTER TABLE opportunity_documents ENABLE ROW LEVEL SECURITY;
-- Users can only view their own opportunity-document links
-- (implicitly through opportunity ownership)
CREATE POLICY "Users can view own opportunity_documents" ON opportunity_documents
FOR SELECT USING (
opportunity_id IN (
SELECT id FROM opportunities WHERE user_id IN (
SELECT id FROM users WHERE clerk_id = current_setting('request.jwt.claims', true)::json->>'sub'
)
)
);
-- Users can create links for their own opportunities
CREATE POLICY "Users can create own opportunity_documents" ON opportunity_documents
FOR INSERT WITH CHECK (
opportunity_id IN (
SELECT id FROM opportunities WHERE user_id IN (
SELECT id FROM users WHERE clerk_id = current_setting('request.jwt.claims', true)::json->>'sub'
)
)
);
-- Users can delete their own links
CREATE POLICY "Users can delete own opportunity_documents" ON opportunity_documents
FOR DELETE USING (
opportunity_id IN (
SELECT id FROM opportunities WHERE user_id IN (
SELECT id FROM users WHERE clerk_id = current_setting('request.jwt.claims', true)::json->>'sub'
)
)
);
-- =============================================================================
-- Indexes for performance
-- =============================================================================
CREATE INDEX IF NOT EXISTS idx_documents_user_id ON documents(user_id);
CREATE INDEX IF NOT EXISTS idx_documents_type ON documents(type);
CREATE INDEX IF NOT EXISTS idx_opportunity_documents_opportunity_id ON opportunity_documents(opportunity_id);
CREATE INDEX IF NOT EXISTS idx_opportunity_documents_document_id ON opportunity_documents(document_id);
-- =============================================================================
-- Updated_at trigger for documents
-- =============================================================================
DROP TRIGGER IF EXISTS update_documents_updated_at ON documents;
CREATE TRIGGER update_documents_updated_at
BEFORE UPDATE ON documents
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- =============================================================================
-- Auto-unlink documents when opportunity status changes to 'rejected'
-- This trigger fires AFTER opportunity update and removes the document links
-- (from opportunity_documents table). The document files remain for reuse.
-- =============================================================================
CREATE OR REPLACE FUNCTION auto_unlink_documents_on_rejection()
RETURNS TRIGGER AS $$
BEGIN
-- Only run when status changes TO 'rejected'
IF NEW.status = 'rejected' AND (OLD.status IS NULL OR OLD.status != 'rejected') THEN
-- Delete the links (documents themselves remain for potential reuse)
DELETE FROM opportunity_documents WHERE opportunity_id = NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';
DROP TRIGGER IF EXISTS unlink_documents_on_rejection ON opportunities;
CREATE TRIGGER unlink_documents_on_rejection
AFTER UPDATE ON opportunities
FOR EACH ROW
EXECUTE FUNCTION auto_unlink_documents_on_rejection();
-- =============================================================================
-- Supabase Storage Bucket (run in Supabase Dashboard > SQL Editor)
-- This creates a private bucket for document uploads
-- =============================================================================
-- Note: Storage bucket creation is typically done via Supabase Dashboard or API
-- The following is for documentation purposes:
--
-- 1. Go to Supabase Dashboard > Storage
-- 2. Create a new bucket called "documents"
-- 3. Set it to PRIVATE (not public)
-- 4. Add the following RLS policy for the bucket:
--
-- Policy name: "Users can upload own documents"
-- Target roles: authenticated
-- WITH CHECK expression:
-- (bucket_id = 'documents' AND (storage.foldername(name))[1] = auth.uid()::text)
--
-- Policy name: "Users can read own documents"
-- Target roles: authenticated
-- USING expression:
-- (bucket_id = 'documents' AND (storage.foldername(name))[1] = auth.uid()::text)
--
-- Policy name: "Users can delete own documents"
-- Target roles: authenticated
-- USING expression:
-- (bucket_id = 'documents' AND (storage.foldername(name))[1] = auth.uid()::text)
-- =============================================================================
-- ATS score columns (for existing deployments that already have documents table)
-- =============================================================================
ALTER TABLE documents
ADD COLUMN IF NOT EXISTS ats_score INTEGER,
ADD COLUMN IF NOT EXISTS ats_analyzed_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS ats_analysis JSONB;