jat-feedback 2.0.0 → 3.0.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.
@@ -4,6 +4,12 @@ import { createClient } from "https://esm.sh/@supabase/supabase-js@2"
4
4
  const supabaseUrl = Deno.env.get("SUPABASE_URL")!
5
5
  const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
6
6
 
7
+ // JAT sends the project's JWT service role key as the Bearer token.
8
+ // On newer Supabase projects, SUPABASE_SERVICE_ROLE_KEY in the runtime
9
+ // may use the sb_secret_ format instead of the JWT format. JAT_WEBHOOK_SECRET
10
+ // is a custom secret set to the JWT service role key for those projects.
11
+ const webhookSecret = Deno.env.get("JAT_WEBHOOK_SECRET") || supabaseServiceKey
12
+
7
13
  /**
8
14
  * JAT Webhook — generic status-sync endpoint.
9
15
  *
@@ -12,24 +18,34 @@ const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
12
18
  * `supabase/functions/jat-webhook/` directory and deploy it.
13
19
  *
14
20
  * Deploy:
15
- * supabase functions deploy jat-webhook
21
+ * supabase functions deploy jat-webhook --no-verify-jwt
22
+ *
23
+ * Setup (if auth fails with "Invalid authorization"):
24
+ * Your project's SUPABASE_SERVICE_ROLE_KEY runtime var may use the
25
+ * new sb_secret_ format. Set JAT_WEBHOOK_SECRET to the JWT service
26
+ * role key from your project's API settings:
16
27
  *
17
- * Expected payload from JAT ingest daemon:
28
+ * supabase secrets set JAT_WEBHOOK_SECRET="eyJhbG..."
29
+ *
30
+ * Expected payload:
18
31
  * {
19
32
  * source: "jat",
20
33
  * event: "status_changed" | "task_closed",
21
- * reference_table: "feedback_reports",
34
+ * reference_table: "project_tasks",
22
35
  * reference_id: "uuid-of-row",
23
36
  * data: {
24
37
  * status: "in_progress" | "completed" | ...,
25
38
  * task_id: "myproject-abc",
26
- * notes?: "optional developer note"
39
+ * notes?: "optional developer note",
40
+ * issue_type?: "bug" | "feature" | "task" | "epic",
41
+ * assignee?: "agent or user name",
42
+ * labels?: ["label1", "label2"],
43
+ * due_date?: "2026-04-01T00:00:00Z",
44
+ * source?: "jat"
27
45
  * }
28
46
  * }
29
47
  *
30
- * Auth: Bearer token must match the Supabase service role key.
31
- * SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are injected automatically
32
- * by Supabase when the function runs — no configuration needed.
48
+ * Auth: Bearer token must match JAT_WEBHOOK_SECRET or SUPABASE_SERVICE_ROLE_KEY.
33
49
  *
34
50
  * integrations.json callback config:
35
51
  * {
@@ -41,7 +57,7 @@ const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
41
57
  * "in_progress": "in_progress",
42
58
  * "closed": "completed"
43
59
  * },
44
- * "referenceTable": "feedback_reports",
60
+ * "referenceTable": "project_tasks",
45
61
  * "referenceIdFrom": "item_id"
46
62
  * }
47
63
  * }
@@ -56,12 +72,19 @@ interface WebhookPayload {
56
72
  status?: string
57
73
  task_id?: string
58
74
  notes?: string
75
+ issue_type?: string
76
+ assignee?: string
77
+ labels?: string[]
78
+ due_date?: string
79
+ source?: string
59
80
  }
60
81
  }
61
82
 
62
83
  // Tables and the columns that JAT is allowed to update.
63
84
  // Add more tables here if you want JAT to sync status for other record types.
85
+ // "feedback_reports" kept as alias for backward compat with pre-v3 callers.
64
86
  const TABLE_CONFIG: Record<string, { statusCol: string; taskIdCol: string }> = {
87
+ project_tasks: { statusCol: "status", taskIdCol: "jat_task_id" },
65
88
  feedback_reports: { statusCol: "status", taskIdCol: "jat_task_id" },
66
89
  }
67
90
 
@@ -82,7 +105,7 @@ Deno.serve(async (req) => {
82
105
  })
83
106
  }
84
107
  const token = authHeader.slice(7)
85
- if (token !== supabaseServiceKey) {
108
+ if (token !== webhookSecret && token !== supabaseServiceKey) {
86
109
  return new Response(JSON.stringify({ error: "Invalid authorization" }), {
87
110
  status: 403,
88
111
  headers: { "Content-Type": "application/json" },
@@ -116,11 +139,19 @@ Deno.serve(async (req) => {
116
139
  )
117
140
  }
118
141
 
142
+ // Resolve actual table name — "feedback_reports" alias maps to "project_tasks"
143
+ const actualTable = reference_table === "feedback_reports" ? "project_tasks" : reference_table
144
+
119
145
  // Build the update object from the payload fields
120
146
  const update: Record<string, unknown> = {}
121
147
  if (data.status) update[config.statusCol] = data.status
122
148
  if (data.task_id) update[config.taskIdCol] = data.task_id
123
149
  if (data.notes) update.dev_notes = data.notes
150
+ if (data.issue_type) update.issue_type = data.issue_type
151
+ if (data.assignee) update.assignee = data.assignee
152
+ if (data.labels) update.labels = data.labels
153
+ if (data.due_date) update.due_date = data.due_date
154
+ if (data.source) update.source = data.source
124
155
 
125
156
  if (Object.keys(update).length === 0) {
126
157
  return new Response(
@@ -130,30 +161,30 @@ Deno.serve(async (req) => {
130
161
  }
131
162
 
132
163
  const supabase = createClient(supabaseUrl, supabaseServiceKey)
133
- const { data, error } = await supabase
134
- .from(reference_table)
164
+ const result = await supabase
165
+ .from(actualTable)
135
166
  .update(update)
136
167
  .eq("id", reference_id)
137
168
  .select("id")
138
169
 
139
- if (error) {
140
- console.error(`JAT webhook failed: ${error.message}`, {
141
- reference_table,
170
+ if (result.error) {
171
+ console.error(`JAT webhook failed: ${result.error.message}`, {
172
+ table: actualTable,
142
173
  reference_id,
143
174
  update,
144
175
  })
145
176
  return new Response(
146
- JSON.stringify({ error: `Update failed: ${error.message}` }),
177
+ JSON.stringify({ error: `Update failed: ${result.error.message}` }),
147
178
  { status: 500, headers: { "Content-Type": "application/json" } },
148
179
  )
149
180
  }
150
181
 
151
- if (!data || data.length === 0) {
152
- console.warn(`JAT webhook: no rows matched ${reference_table}[${reference_id}]`, update)
182
+ if (!result.data || result.data.length === 0) {
183
+ console.warn(`JAT webhook: no rows matched ${actualTable}[${reference_id}]`, update)
153
184
  return new Response(
154
185
  JSON.stringify({
155
- error: `No rows matched: ${reference_table} id=${reference_id}`,
156
- table: reference_table,
186
+ error: `No rows matched: ${actualTable} id=${reference_id}`,
187
+ table: actualTable,
157
188
  id: reference_id,
158
189
  rowsAffected: 0,
159
190
  }),
@@ -161,15 +192,15 @@ Deno.serve(async (req) => {
161
192
  )
162
193
  }
163
194
 
164
- console.log(`JAT webhook: ${event} → ${reference_table}[${reference_id}] (${data.length} row(s))`, update)
195
+ console.log(`JAT webhook: ${event} → ${actualTable}[${reference_id}] (${result.data.length} row(s))`, update)
165
196
 
166
197
  return new Response(
167
198
  JSON.stringify({
168
199
  success: true,
169
- table: reference_table,
200
+ table: actualTable,
170
201
  id: reference_id,
171
202
  updated: update,
172
- rowsAffected: data.length,
203
+ rowsAffected: result.data.length,
173
204
  }),
174
205
  { status: 200, headers: { "Content-Type": "application/json" } },
175
206
  )
@@ -0,0 +1,170 @@
1
+ -- jat-feedback v3.0.0 — rename feedback_reports → project_tasks + extend schema
2
+ --
3
+ -- BREAKING CHANGE: This migration renames the core table and adds new columns
4
+ -- for unified task management (feedback + JAT tasks + manual entries).
5
+ --
6
+ -- Apply as a new migration in your project:
7
+ -- cp node_modules/jat-feedback/supabase/migrations/3.0.0_rename_to_project_tasks.sql \
8
+ -- supabase/migrations/$(date +%Y%m%d%H%M%S)_feedback_3_0_0.sql
9
+ -- supabase db push
10
+ --
11
+ -- Prerequisites: 1.0.0, 1.1.0, 1.8.0 migrations must already be applied.
12
+
13
+ -- ============================================================================
14
+ -- 1. Rename table
15
+ -- ============================================================================
16
+
17
+ ALTER TABLE feedback_reports RENAME TO project_tasks;
18
+
19
+ -- Rename type → source_type to avoid confusion with issue_type
20
+ ALTER TABLE project_tasks RENAME COLUMN type TO source_type;
21
+
22
+ -- ============================================================================
23
+ -- 2. Add new columns
24
+ -- ============================================================================
25
+
26
+ -- Source of the task: feedback widget, JAT task sync, or manual creation
27
+ ALTER TABLE project_tasks ADD COLUMN source TEXT DEFAULT 'feedback'
28
+ CHECK (source IN ('feedback', 'jat', 'manual'));
29
+
30
+ -- Issue classification (extends the original bug/enhancement/other type)
31
+ ALTER TABLE project_tasks ADD COLUMN issue_type TEXT DEFAULT 'bug'
32
+ CHECK (issue_type IN ('bug', 'feature', 'task', 'epic'));
33
+
34
+ -- Assignment and scheduling
35
+ ALTER TABLE project_tasks ADD COLUMN assignee TEXT;
36
+ ALTER TABLE project_tasks ADD COLUMN due_date TIMESTAMPTZ;
37
+
38
+ -- Labels for flexible categorization
39
+ ALTER TABLE project_tasks ADD COLUMN labels TEXT[];
40
+
41
+ -- Self-referential parent for hierarchical tasks (epics → children)
42
+ ALTER TABLE project_tasks ADD COLUMN parent_id UUID REFERENCES project_tasks(id);
43
+
44
+ -- Updated timestamp
45
+ ALTER TABLE project_tasks ADD COLUMN updated_at TIMESTAMPTZ DEFAULT now();
46
+
47
+ -- ============================================================================
48
+ -- 3. Auto-update updated_at trigger
49
+ -- ============================================================================
50
+
51
+ CREATE OR REPLACE FUNCTION update_project_tasks_updated_at()
52
+ RETURNS TRIGGER AS $$
53
+ BEGIN
54
+ NEW.updated_at = now();
55
+ RETURN NEW;
56
+ END;
57
+ $$ LANGUAGE plpgsql;
58
+
59
+ CREATE TRIGGER project_tasks_updated_at
60
+ BEFORE UPDATE ON project_tasks
61
+ FOR EACH ROW
62
+ EXECUTE FUNCTION update_project_tasks_updated_at();
63
+
64
+ -- ============================================================================
65
+ -- 4. Create project_tasks_comments table
66
+ -- ============================================================================
67
+
68
+ CREATE TABLE IF NOT EXISTS project_tasks_comments (
69
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
70
+ task_id UUID NOT NULL REFERENCES project_tasks(id) ON DELETE CASCADE,
71
+ author TEXT NOT NULL,
72
+ author_role TEXT DEFAULT 'user',
73
+ text TEXT NOT NULL DEFAULT '',
74
+ created_at TIMESTAMPTZ DEFAULT now()
75
+ );
76
+
77
+ CREATE INDEX idx_project_tasks_comments_task
78
+ ON project_tasks_comments(task_id, created_at);
79
+
80
+ -- RLS for comments
81
+ ALTER TABLE project_tasks_comments ENABLE ROW LEVEL SECURITY;
82
+
83
+ CREATE POLICY "Authenticated users can insert comments"
84
+ ON project_tasks_comments FOR INSERT TO authenticated
85
+ WITH CHECK (true);
86
+
87
+ CREATE POLICY "Authenticated users can read comments"
88
+ ON project_tasks_comments FOR SELECT TO authenticated
89
+ USING (true);
90
+
91
+ CREATE POLICY "Service role full access to comments"
92
+ ON project_tasks_comments FOR ALL TO service_role
93
+ USING (true) WITH CHECK (true);
94
+
95
+ -- ============================================================================
96
+ -- 5. Rename indexes (drop old, create new)
97
+ -- ============================================================================
98
+
99
+ -- The table rename does NOT automatically rename indexes.
100
+ DROP INDEX IF EXISTS idx_feedback_reports_new;
101
+ DROP INDEX IF EXISTS idx_feedback_reports_rejected;
102
+ DROP INDEX IF EXISTS idx_feedback_reports_reporter;
103
+ DROP INDEX IF EXISTS idx_feedback_reports_completed;
104
+
105
+ -- Recreate with new names
106
+ CREATE INDEX idx_project_tasks_new
107
+ ON project_tasks(status, jat_task_id)
108
+ WHERE status = 'submitted' AND jat_task_id IS NULL;
109
+
110
+ CREATE INDEX idx_project_tasks_rejected
111
+ ON project_tasks(status)
112
+ WHERE status = 'rejected';
113
+
114
+ CREATE INDEX idx_project_tasks_reporter
115
+ ON project_tasks(reporter_user_id, created_at DESC);
116
+
117
+ CREATE INDEX idx_project_tasks_completed
118
+ ON project_tasks(reporter_user_id, status)
119
+ WHERE status = 'completed';
120
+
121
+ -- ============================================================================
122
+ -- 6. New indexes for extended schema
123
+ -- ============================================================================
124
+
125
+ -- Filter by source (feedback vs jat vs manual)
126
+ CREATE INDEX idx_project_tasks_source
127
+ ON project_tasks(source);
128
+
129
+ -- Parent-child lookups
130
+ CREATE INDEX idx_project_tasks_parent
131
+ ON project_tasks(parent_id)
132
+ WHERE parent_id IS NOT NULL;
133
+
134
+ -- Assignee lookups
135
+ CREATE INDEX idx_project_tasks_assignee
136
+ ON project_tasks(assignee)
137
+ WHERE assignee IS NOT NULL;
138
+
139
+ -- Due date ordering
140
+ CREATE INDEX idx_project_tasks_due_date
141
+ ON project_tasks(due_date)
142
+ WHERE due_date IS NOT NULL;
143
+
144
+ -- ============================================================================
145
+ -- 7. Update RLS policies (rename references from feedback_reports)
146
+ -- ============================================================================
147
+
148
+ -- Drop old policies (they reference the old table name internally)
149
+ DROP POLICY IF EXISTS "Authenticated users can insert feedback" ON project_tasks;
150
+ DROP POLICY IF EXISTS "Users can read own feedback reports" ON project_tasks;
151
+ DROP POLICY IF EXISTS "Users can respond to own completed feedback" ON project_tasks;
152
+ DROP POLICY IF EXISTS "Service role full access to feedback" ON project_tasks;
153
+
154
+ -- Recreate with updated names
155
+ CREATE POLICY "Authenticated users can insert project tasks"
156
+ ON project_tasks FOR INSERT TO authenticated
157
+ WITH CHECK (true);
158
+
159
+ CREATE POLICY "Users can read own project tasks"
160
+ ON project_tasks FOR SELECT TO authenticated
161
+ USING (reporter_user_id = auth.uid());
162
+
163
+ CREATE POLICY "Users can update own project tasks"
164
+ ON project_tasks FOR UPDATE TO authenticated
165
+ USING (reporter_user_id = auth.uid())
166
+ WITH CHECK (reporter_user_id = auth.uid());
167
+
168
+ CREATE POLICY "Service role full access to project tasks"
169
+ ON project_tasks FOR ALL TO service_role
170
+ USING (true) WITH CHECK (true);