-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathschema_logic.sql
More file actions
152 lines (132 loc) · 5.78 KB
/
schema_logic.sql
File metadata and controls
152 lines (132 loc) · 5.78 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
-- ########## STORAGE CLEANUP FUNCTIONS ##########
-- Deletes audio file from storage when a diary entry is removed
create or replace function delete_file_when_entry_deleted()
returns trigger language plpgsql security definer as $$
begin
if OLD.audio_path is not null then
delete from storage.objects where bucket_id = 'audio-recordings' and name = OLD.audio_path;
end if;
return OLD;
end;
$$;
-- Deletes audio file from storage when the path is set to null during an update
create or replace function delete_file_on_unlink()
returns trigger language plpgsql security definer as $$
begin
if OLD.audio_path is not null and NEW.audio_path is null then
delete from storage.objects where bucket_id = 'audio-recordings' and name = OLD.audio_path;
end if;
return NEW;
end;
$$;
-- ########## DATABASE TRIGGERS ##########
-- Fire cleanup on entry deletion
drop trigger if exists on_entry_delete_cleanup on public."DiaryEntry";
create trigger on_entry_delete_cleanup after delete on public."DiaryEntry"
for each row execute function delete_file_when_entry_deleted();
-- Fire cleanup when audio is unlinked from an entry
drop trigger if exists on_audio_unlink on public."DiaryEntry";
create trigger on_audio_unlink after update on public."DiaryEntry"
for each row execute function delete_file_on_unlink();
-- ########## ANALYTICS & INSIGHTS ##########
-- Compiles chart, heatmap, and topic data into a single JSON object
create or replace function get_insights(arg_user_id text, days_to_avg int, loopback_day int)
returns json language plpgsql as $$
declare
insight_data json;
begin
select json_build_object(
-- Chart Data: Mood scores over the loopback period
'chartData', (
select coalesce(json_agg(row_to_json(c)), '[]')
from (
select mood_scores, created_at
from "DiaryEntry" d
where d.user_id = arg_user_id
and d.created_at >= (current_date - (loopback_day * INTERVAL '1 day'))
order by created_at asc
) c
),
-- Heatmap Data: Daily averages for all mood categories
'heatmapData', (
select coalesce(json_agg(row_to_json(h)), '[]')
from (
select
created_at::date as day,
jsonb_build_object(
'joy', round(avg((mood_scores->>'joy')::numeric), 2),
'sadness', round(avg((mood_scores->>'sadness')::numeric), 2),
'anger', round(avg((mood_scores->>'anger')::numeric), 2),
'fear', round(avg((mood_scores->>'fear')::numeric), 2),
'surprise', round(avg((mood_scores->>'surprise')::numeric), 2),
'love', round(avg((mood_scores->>'love')::numeric), 2),
'calm', round(avg((mood_scores->>'calm')::numeric), 2)
) as moods
from "DiaryEntry" d
where d.user_id = arg_user_id
and d.created_at >= (current_date - (days_to_avg * INTERVAL '1 day'))
group by created_at::date
order by created_at::date desc
) h
),
-- Top Topics: Most frequent tags used in the loopback period
'topics', (
select coalesce(json_agg(row_to_json(t)), '[]')
from (
select tag as topic, count(*) as count
from "DiaryEntry" d, unnest(d.tags) as tag
where d.user_id = arg_user_id
and d.created_at >= (current_date - (loopback_day * INTERVAL '1 day'))
group by tag
order by count desc
limit 5
) t
)
) into insight_data;
return insight_data;
end;
$$;
-- Create the DiaryEntry table with its custom types, schema, and optimized indexes.
DO $$ BEGIN
CREATE TYPE audio_status AS ENUM ('processing', 'completed', 'failed');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
CREATE TABLE public."DiaryEntry" (
entry_id uuid DEFAULT gen_random_uuid() NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL,
user_id text NOT NULL,
title text,
transcript text,
ai_summary text,
reflections text,
suggestions text,
goals text,
tags text[] DEFAULT '{}'::text[],
mood_labels text[] DEFAULT '{}'::text[],
mood_scores jsonb DEFAULT '{}'::jsonb,
audio_path text NOT NULL,
status audio_status DEFAULT 'processing'::audio_status,
"isGoalAdded" boolean DEFAULT false,
CONSTRAINT "DiaryEntry_pkey" PRIMARY KEY (entry_id),
CONSTRAINT "DiaryEntry_audio_path_unique" UNIQUE (audio_path)
);
CREATE UNIQUE INDEX IF NOT EXISTS "DiaryEntry_audio_id_key" ON public."DiaryEntry" USING btree (entry_id);
CREATE UNIQUE INDEX IF NOT EXISTS "DiaryEntry_audio_url_key" ON public."DiaryEntry" USING btree (audio_path);
CREATE INDEX IF NOT EXISTS idx_diary_entry_user_recent ON public."DiaryEntry" USING btree (user_id, created_at DESC);
-- Creates the GoalEntry table for tracking user-defined or AI-suggested goals.
CREATE TABLE public."GoalEntry" (
goal_id uuid DEFAULT gen_random_uuid() NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL,
user_id text NOT NULL,
title text NOT NULL,
description text,
is_completed boolean DEFAULT false NOT NULL,
target_date date NOT NULL,
entry_id uuid,
CONSTRAINT "GoalEntry_pkey" PRIMARY KEY (goal_id),
CONSTRAINT "GoalEntry_title_key" UNIQUE (title),
CONSTRAINT "GoalEntry_entry_id_fkey" FOREIGN KEY (entry_id) REFERENCES public."DiaryEntry"(entry_id) ON DELETE SET NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS "GoalEntries_name_key" ON public."GoalEntry" USING btree (title);
CREATE UNIQUE INDEX IF NOT EXISTS "GoalEntries_pkey" ON public."GoalEntry" USING btree (goal_id);