-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathschema.sql
More file actions
282 lines (255 loc) · 8.55 KB
/
schema.sql
File metadata and controls
282 lines (255 loc) · 8.55 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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
-- Enable PostGIS extension
CREATE EXTENSION IF NOT EXISTS postgis;
-- Users table
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
first_name TEXT,
last_name TEXT,
username TEXT UNIQUE,
mobile_number TEXT,
id_type TEXT,
id_value TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- User verification table
CREATE TABLE IF NOT EXISTS user_verification (
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
verification_status TEXT DEFAULT 'pending',
id_document_url TEXT,
selfie_url TEXT,
verified_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);
-- Civic issues table
CREATE TABLE IF NOT EXISTS civic_issues (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES users(id),
category TEXT NOT NULL,
description TEXT,
location geography(POINT, 4326), -- PostGIS geography for GPS coordinates
address TEXT,
media_files TEXT[], -- Array of URLs to media files in Supabase storage
voice_note_url TEXT,
priority_score INTEGER DEFAULT 0,
upvotes INTEGER DEFAULT 0,
assigned_department TEXT,
status TEXT DEFAULT 'reported',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Issue upvotes table (for tracking who upvoted what)
CREATE TABLE IF NOT EXISTS issue_upvotes (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
issue_id UUID REFERENCES civic_issues(id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id, issue_id) -- Prevent duplicate upvotes
);
-- Issue clustering table (for grouping similar issues)
CREATE TABLE IF NOT EXISTS issue_clusters (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
primary_issue_id UUID REFERENCES civic_issues(id),
cluster_radius INTEGER DEFAULT 15, -- in meters
created_at TIMESTAMP DEFAULT NOW()
);
-- Cluster members table (issues that belong to a cluster)
CREATE TABLE IF NOT EXISTS cluster_members (
cluster_id UUID REFERENCES issue_clusters(id),
issue_id UUID REFERENCES civic_issues(id),
added_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (cluster_id, issue_id)
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_civic_issues_location ON civic_issues USING GIST (location);
CREATE INDEX IF NOT EXISTS idx_civic_issues_category ON civic_issues (category);
CREATE INDEX IF NOT EXISTS idx_civic_issues_priority ON civic_issues (priority_score DESC);
CREATE INDEX IF NOT EXISTS idx_civic_issues_created ON civic_issues (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_civic_issues_status ON civic_issues (status);
CREATE INDEX IF NOT EXISTS idx_issue_upvotes_issue ON issue_upvotes (issue_id);
CREATE INDEX IF NOT EXISTS idx_issue_upvotes_user ON issue_upvotes (user_id);
-- Function to calculate priority score
CREATE OR REPLACE FUNCTION calculate_priority_score(issue_id UUID)
RETURNS INTEGER AS $$
DECLARE
report_count INTEGER;
upvote_count INTEGER;
category_multiplier INTEGER;
age_bonus INTEGER;
severity_weight INTEGER;
base_score INTEGER;
BEGIN
-- Get basic counts
SELECT COUNT(*) INTO report_count
FROM cluster_members cm1
JOIN cluster_members cm2 ON cm1.cluster_id = cm2.cluster_id
WHERE cm2.issue_id = $1;
SELECT upvotes INTO upvote_count FROM civic_issues WHERE id = $1;
-- Category multipliers
SELECT CASE
WHEN category = 'POWER_CUT' THEN 5
WHEN category = 'WATER_LEAK' THEN 4
WHEN category = 'SEWAGE_OVERFLOW' THEN 4
WHEN category IN ('POTHOLE', 'GARBAGE') THEN 1
ELSE 1
END INTO category_multiplier
FROM civic_issues WHERE id = $1;
-- Age bonus (newer issues get bonus)
SELECT CASE
WHEN EXTRACT(DAY FROM (NOW() - created_at)) < 1 THEN 10
WHEN EXTRACT(DAY FROM (NOW() - created_at)) < 7 THEN 5
ELSE 0
END INTO age_bonus
FROM civic_issues WHERE id = $1;
-- Severity weight (could be extended)
severity_weight := 0;
-- Calculate base score
base_score := (COALESCE(report_count, 1) * 2) +
(COALESCE(upvote_count, 0) * 3) +
category_multiplier +
age_bonus +
severity_weight;
RETURN base_score;
END;
$$ LANGUAGE plpgsql;
-- Trigger to update priority score when upvotes change
CREATE OR REPLACE FUNCTION update_priority_score()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
UPDATE civic_issues
SET priority_score = calculate_priority_score(NEW.issue_id),
updated_at = NOW()
WHERE id = NEW.issue_id;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
UPDATE civic_issues
SET priority_score = calculate_priority_score(OLD.issue_id),
updated_at = NOW()
WHERE id = OLD.issue_id;
RETURN OLD;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- Trigger for upvotes
DROP TRIGGER IF EXISTS trigger_update_priority_on_upvote ON issue_upvotes;
CREATE TRIGGER trigger_update_priority_on_upvote
AFTER INSERT OR UPDATE OR DELETE ON issue_upvotes
FOR EACH ROW EXECUTE FUNCTION update_priority_score();
-- Function to find issues within radius
CREATE OR REPLACE FUNCTION get_issues_within_radius(
user_lat DOUBLE PRECISION,
user_lng DOUBLE PRECISION,
radius_km INTEGER DEFAULT 15
)
RETURNS TABLE (
id UUID,
category TEXT,
description TEXT,
address TEXT,
upvotes INTEGER,
priority_score INTEGER,
status TEXT,
created_at TIMESTAMP,
distance_km DOUBLE PRECISION
) AS $$
BEGIN
RETURN QUERY
SELECT
ci.id,
ci.category,
ci.description,
ci.address,
ci.upvotes,
ci.priority_score,
ci.status,
ci.created_at,
ST_Distance(
ci.location,
ST_SetSRID(ST_MakePoint(user_lng, user_lat), 4326)::GEOGRAPHY
) / 1000 AS distance_km
FROM civic_issues ci
WHERE ST_DWithin(
ci.location,
ST_SetSRID(ST_MakePoint(user_lng, user_lat), 4326)::GEOGRAPHY,
radius_km * 1000 -- Convert km to meters
)
AND ci.status != 'completed'
ORDER BY ci.priority_score DESC, ci.created_at DESC;
END;
$$ LANGUAGE plpgsql;
-- Function to upvote an issue
DROP FUNCTION IF EXISTS upvote_issue(UUID, UUID);
CREATE OR REPLACE FUNCTION upvote_issue(p_user_id UUID, p_issue_id UUID)
RETURNS INTEGER AS $$
DECLARE
current_upvotes INTEGER;
BEGIN
-- Insert upvote if not exists
INSERT INTO issue_upvotes (user_id, issue_id)
VALUES (p_user_id, p_issue_id)
ON CONFLICT (user_id, issue_id) DO NOTHING;
-- Update the upvotes count in civic_issues
UPDATE civic_issues
SET upvotes = (SELECT COUNT(*) FROM issue_upvotes WHERE issue_upvotes.issue_id = p_issue_id)
WHERE id = p_issue_id
RETURNING upvotes INTO current_upvotes;
RETURN current_upvotes;
END;
$$ LANGUAGE plpgsql;
-- Function to create a new issue
CREATE OR REPLACE FUNCTION create_civic_issue(
user_id UUID,
category TEXT,
description TEXT,
lat DOUBLE PRECISION,
lng DOUBLE PRECISION,
address TEXT,
media_urls TEXT[],
voice_note_url TEXT
)
RETURNS UUID AS $$
DECLARE
new_issue_id UUID;
location_geom GEOGRAPHY;
BEGIN
-- Create geography point from lat/lng
location_geom := ST_SetSRID(ST_MakePoint(lng, lat), 4326)::GEOGRAPHY;
-- Insert new issue
INSERT INTO civic_issues (
user_id, category, description, location, address,
media_files, voice_note_url, priority_score
) VALUES (
user_id, category, description, location_geom, address,
media_urls, voice_note_url, 0
) RETURNING id INTO new_issue_id;
-- Initialize priority score
UPDATE civic_issues
SET priority_score = calculate_priority_score(new_issue_id)
WHERE id = new_issue_id;
RETURN new_issue_id;
END;
$$ LANGUAGE plpgsql;
-- Function to create a public.users entry when a new auth.users entry is created
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.users (id, first_name, last_name, username, mobile_number, id_type, id_value)
VALUES (
NEW.id,
NULL,
NULL,
NEW.email,
NULL,
NULL,
NULL
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Trigger to call handle_new_user() when a new user is created in auth.users
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();