* Push Notification Setup and attempt

* Office Ordering
* Allow editing of Task and Ticket Details after creation
This commit is contained in:
2026-02-24 21:06:46 +08:00
parent cc6fda0e79
commit 5979a04254
25 changed files with 1130 additions and 91 deletions
+77
View File
@@ -0,0 +1,77 @@
import { createClient } from 'npm:@supabase/supabase-js@2';
// we no longer use google-auth-library or a service account; instead
// send FCM via a legacy server key provided as an environment variable
// (FCM_SERVER_KEY). This avoids compatibility problems on the edge.
const supabase = createClient(Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!);
Deno.serve(async (req) => {
try {
await supabase.from('send_fcm_errors').insert({ payload: null, error: 'step1', stack: null });
const body = await req.json();
try { await supabase.from('send_fcm_errors').insert({ payload: body, error: 'step2', stack: null }); } catch (_) {}
// gather tokens (same as before)
let tokens: any[] = [];
if (Array.isArray(body.tokens)) {
tokens = body.tokens;
} else if (Array.isArray(body.user_ids)) {
try {
const { data: rows } = await supabase
.from('fcm_tokens')
.select('token')
.in('user_id', body.user_ids);
if (rows) tokens = rows.map((r: any) => r.token);
try { await supabase.from('send_fcm_errors').insert({ payload: rows ?? null, error: 'step4a', stack: null }); } catch (_) {}
} catch (_) {}
}
if (body.record && body.record.user_id) {
try {
const { data: rows } = await supabase
.from('fcm_tokens')
.select('token')
.eq('user_id', body.record.user_id);
if (rows) tokens = rows.map((r: any) => r.token);
try { await supabase.from('send_fcm_errors').insert({ payload: rows ?? null, error: 'step4b', stack: null }); } catch (_) {}
} catch (_) {}
}
try { await supabase.from('send_fcm_errors').insert({ payload: tokens, error: 'step3', stack: null }); } catch (_) {}
// build notification text
let notificationTitle = '';
let notificationBody = '';
if (body.record) {
notificationTitle = `New ${body.record.type}`;
notificationBody = 'You have a new update in TasQ.';
if (body.record.ticket_id) {
notificationBody = 'You have a new update on your ticket.';
} else if (body.record.task_id) {
notificationBody = 'You have a new update on your task.';
}
}
try { await supabase.from('send_fcm_errors').insert({ payload: {title: notificationTitle, body: notificationBody}, error: 'step5', stack: null }); } catch (_) {}
// use legacy server key for FCM send
const serverKey = Deno.env.get('FCM_SERVER_KEY');
if (serverKey && tokens.length) {
const sendPromises = tokens.map((tok) => {
return fetch('https://fcm.googleapis.com/fcm/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `key=${serverKey}`,
},
body: JSON.stringify({ to: tok, notification: { title: notificationTitle, body: notificationBody }, data: body.data || {} }),
});
});
try {
const results = await Promise.all(sendPromises);
try { await supabase.from('send_fcm_errors').insert({ payload: results, error: 'step8', stack: null }); } catch (_) {}
} catch (e) {
try { await supabase.from('send_fcm_errors').insert({ payload: e.toString(), error: 'step8error', stack: null }); } catch (_) {}
}
}
} catch (_) {}
return new Response('ok', { headers: { 'Access-Control-Allow-Origin': '*' } });
});
@@ -0,0 +1,11 @@
-- create a table to hold FCM device tokens for push notifications
create table if not exists public.fcm_tokens (
id uuid default uuid_generate_v4() primary key,
user_id uuid references auth.users(id) on delete cascade,
token text not null,
created_at timestamptz not null default now()
);
create unique index if not exists fcm_tokens_user_token_idx
on public.fcm_tokens(user_id, token);
@@ -0,0 +1,20 @@
-- enable RLS and set policies for fcm_tokens
ALTER TABLE public.fcm_tokens ENABLE ROW LEVEL SECURITY;
-- allow users to insert their own tokens
CREATE POLICY "Allow users insert their tokens" ON public.fcm_tokens
FOR INSERT WITH CHECK (auth.uid() = user_id);
-- allow users to delete their own tokens (e.g. sign out)
CREATE POLICY "Allow users delete their tokens" ON public.fcm_tokens
FOR DELETE USING (auth.uid() = user_id);
-- allow users to select their own tokens (if needed for debugging)
CREATE POLICY "Allow users select their tokens" ON public.fcm_tokens
FOR SELECT USING (auth.uid() = user_id);
-- optionally allow update of token value for same user
CREATE POLICY "Allow users update their tokens" ON public.fcm_tokens
FOR UPDATE USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
@@ -0,0 +1,8 @@
-- drop unique index to allow multiple entries (e.g. same token across
-- device logins) for a single user. the application already keeps tokens
-- tidy by unregistering on sign-out, so duplicates are harmless.
drop index if exists fcm_tokens_user_token_idx;
-- maintain a regular index on user_id for performance
create index if not exists fcm_tokens_user_idx on public.fcm_tokens(user_id);
@@ -0,0 +1,35 @@
-- create a trigger that calls the `send_fcm` edge function whenever a
-- new row is inserted into `notifications`. This moves push logic entirely
-- to the backend, bypassing any CORS or auth issues that occur when web
-- clients try to invoke the function directly.
-- the http extension is available on Supabase databases; enable it if
-- it's not already installed.
create extension if not exists http;
create or replace function public.notifications_send_fcm_trigger()
returns trigger
language plpgsql as $$
declare
_url text := 'https://pwbxgsuskvqwwaejxutj.supabase.co/functions/v1/send_fcm';
_body text;
begin
-- build a webhook-style payload that matches what the edge function
-- expects when it's invoked from a database trigger.
_body := json_build_object('record', row_to_json(NEW))::text;
-- fire the POST; ignore the result since we don't want inserts to fail
-- just because the push call returned an error.
perform http_post(_url, _body, 'content-type=application/json');
return NEW;
end;
$$;
-- drop any previous trigger (defensive) and create a new one.
drop trigger if exists trig_notifications_send_fcm on public.notifications;
create trigger trig_notifications_send_fcm
after insert on public.notifications
for each row
execute function public.notifications_send_fcm_trigger();
@@ -0,0 +1,11 @@
-- table for capturing errors that occur inside the send_fcm edge function
-- when it is invoked via database trigger. This makes it easier to debug
-- production failures without needing to inspect external logs.
create table if not exists public.send_fcm_errors (
id uuid default gen_random_uuid() primary key,
payload jsonb,
error text,
stack text,
created_at timestamptz default now()
);