Skip to content

Commit ebbdb9e

Browse files
committed
🥊🛀🏼 ↝ [SSG-270 SSG-280]: Notifications are now logged weekly to prevent duplications
1 parent 9c5275d commit ebbdb9e

File tree

3 files changed

+151
-12
lines changed

3 files changed

+151
-12
lines changed

public/sw.js

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,27 @@ self.addEventListener("push", function (event) {
1313
const title = data?.title || "New Discovery!";
1414
const options = {
1515
body: data?.body || "A new anomaly has been unlocked",
16-
icon: data?.icon || '/assets/Captn.jpg',
17-
badge: data?.icon || '/assets/Captn.jpg',
18-
data: { url: data?.url || "/" },
19-
requireInteraction: true,
20-
silent: false
16+
icon: data?.icon || '/assets/Captn.jpg', // Use Captn.jpg as fallback
17+
badge: data?.badge || '/assets/Captn.jpg', // Badge for Android
18+
image: data?.image || '/assets/Captn.jpg', // Large image for rich notifications
19+
data: {
20+
url: data?.url || "/",
21+
action: 'default'
22+
},
23+
tag: data?.tag || 'discovery-notification', // Prevent notification stacking
24+
requireInteraction: data?.requireInteraction || true,
25+
silent: false,
26+
vibrate: [200, 100, 200], // Vibration pattern for Android
27+
actions: data?.actions || [
28+
{
29+
action: 'classify',
30+
title: '🔬 Classify Now'
31+
},
32+
{
33+
action: 'dismiss',
34+
title: 'Later'
35+
}
36+
]
2137
};
2238

2339
console.log('Showing notification:', title, options);
@@ -34,7 +50,33 @@ self.addEventListener("push", function (event) {
3450
});
3551

3652
self.addEventListener("notificationclick", function (event) {
37-
console.log('Notification clicked:', event.notification);
53+
console.log('Notification clicked:', event.notification, 'Action:', event.action);
54+
3855
event.notification.close();
39-
event.waitUntil(clients.openWindow(event.notification.data.url));
56+
57+
let targetUrl = event.notification.data.url || '/';
58+
59+
// Handle different actions
60+
if (event.action === 'classify') {
61+
targetUrl = '/structures/telescope'; // Direct to classification page
62+
} else if (event.action === 'dismiss') {
63+
// Just close the notification, don't open anything
64+
return;
65+
}
66+
67+
event.waitUntil(
68+
clients.matchAll({ type: 'window', includeUncontrolled: true })
69+
.then(clientList => {
70+
// Check if there's already a window open
71+
for (let client of clientList) {
72+
if (client.url.includes(self.location.origin) && 'focus' in client) {
73+
// Focus existing window and navigate
74+
client.focus();
75+
return client.navigate(targetUrl);
76+
}
77+
}
78+
// No existing window, open a new one
79+
return clients.openWindow(targetUrl);
80+
})
81+
);
4082
});

scripts/notify-unclassified-discoveries.js

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const supabase = createClient(supabaseUrl, supabaseServiceKey);
1919

2020
// Configure webpush
2121
webpush.setVapidDetails(
22-
'mailto:admin@starsailors.app',
22+
'mailto:teddy@scroobl.es',
2323
vapidPublicKey,
2424
vapidPrivateKey
2525
);
@@ -48,6 +48,19 @@ async function findUsersWithUnclassifiedDiscoveries() {
4848

4949
console.log(`Found ${linkedAnomalies?.length || 0} linked anomalies`);
5050

51+
// Get already notified anomalies from push_anomaly_log
52+
const { data: notifiedAnomalies, error: notifiedError } = await supabase
53+
.from('push_anomaly_log')
54+
.select('anomaly_id');
55+
56+
if (notifiedError) {
57+
console.error('Error fetching notified anomalies:', notifiedError);
58+
return [];
59+
}
60+
61+
console.log(`Found ${notifiedAnomalies?.length || 0} already notified anomalies`);
62+
const notifiedAnomalyIds = new Set(notifiedAnomalies?.map(log => log.anomaly_id) || []);
63+
5164
// Get all classifications
5265
const { data: classifications, error: classError } = await supabase
5366
.from('classifications')
@@ -75,12 +88,18 @@ async function findUsersWithUnclassifiedDiscoveries() {
7588
linkedAnomalies?.forEach(linkedAnomaly => {
7689
const userId = linkedAnomaly.author;
7790
const anomalyId = linkedAnomaly.anomaly_id;
91+
const linkedAnomalyId = linkedAnomaly.id;
7892

93+
// Skip if already notified about this linked anomaly
94+
if (notifiedAnomalyIds.has(linkedAnomalyId)) {
95+
return;
96+
}
97+
7998
// Check if this user has classified this specific anomaly
8099
const userClassifications = userClassifiedAnomalies.get(userId) || new Set();
81100

82101
if (!userClassifications.has(anomalyId)) {
83-
// This is an unclassified discovery
102+
// This is an unclassified discovery that hasn't been notified about
84103
if (!usersWithUnclassified.has(userId)) {
85104
usersWithUnclassified.set(userId, []);
86105
}
@@ -94,7 +113,7 @@ async function findUsersWithUnclassifiedDiscoveries() {
94113
}
95114
});
96115

97-
console.log(`Found ${usersWithUnclassified.size} users with unclassified discoveries`);
116+
console.log(`Found ${usersWithUnclassified.size} users with unclassified discoveries (excluding already notified)`);
98117

99118
return Array.from(usersWithUnclassified.entries()).map(([userId, discoveries]) => ({
100119
userId,
@@ -107,6 +126,34 @@ async function findUsersWithUnclassifiedDiscoveries() {
107126
}
108127
}
109128

129+
async function logNotifiedAnomalies(discoveries) {
130+
try {
131+
if (discoveries.length === 0) return;
132+
133+
console.log(`Logging ${discoveries.length} anomalies as notified...`);
134+
135+
const logEntries = discoveries.map(discovery => ({
136+
anomaly_id: discovery.linkedAnomalyId
137+
}));
138+
139+
const { data, error } = await supabase
140+
.from('push_anomaly_log')
141+
.insert(logEntries)
142+
.select();
143+
144+
if (error) {
145+
console.error('Error logging notified anomalies:', error);
146+
return false;
147+
}
148+
149+
console.log(`Successfully logged ${data?.length || 0} anomalies as notified`);
150+
return true;
151+
} catch (error) {
152+
console.error('Error in logNotifiedAnomalies:', error);
153+
return false;
154+
}
155+
}
156+
110157
async function sendNotificationsToUser(userId, discoveries) {
111158
try {
112159
// Get user's push subscriptions with deduplication by endpoint
@@ -151,8 +198,22 @@ async function sendNotificationsToUser(userId, discoveries) {
151198
const payload = JSON.stringify({
152199
title,
153200
body,
154-
icon: '/assets/Captn.jpg',
155-
url: '/structures/telescope' // Or wherever users go to classify
201+
icon: '/assets/Captn.jpg', // Keep using Captn.jpg for both iOS and Android
202+
badge: '/assets/Captn.jpg', // Badge for Android
203+
image: '/assets/Captn.jpg', // Large image for rich notifications
204+
url: '/structures/telescope', // Or wherever users go to classify
205+
tag: 'unclassified-discoveries', // Prevent multiple notifications from stacking
206+
requireInteraction: true, // Keep notification visible until user interacts
207+
actions: [
208+
{
209+
action: 'classify',
210+
title: '🔬 Classify Now'
211+
},
212+
{
213+
action: 'dismiss',
214+
title: 'Later'
215+
}
216+
]
156217
});
157218

158219
// Send notifications to all user's endpoints
@@ -179,6 +240,14 @@ async function sendNotificationsToUser(userId, discoveries) {
179240
const successful = results.filter(r => r.success).length;
180241
const failed = results.filter(r => !r.success).length;
181242

243+
if (successful > 0) {
244+
// Log the anomalies as notified only if at least one notification was successful
245+
const logged = await logNotifiedAnomalies(discoveries);
246+
if (!logged) {
247+
console.warn(`Failed to log anomalies for user ${userId}, but notifications were sent`);
248+
}
249+
}
250+
182251
console.log(`User ${userId}: ${successful} notifications sent, ${failed} failed`);
183252
return { success: true, sent: successful, failed };
184253

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-- Create push_anomaly_log table to track which anomalies have been notified about
2+
create table if not exists public.push_anomaly_log (
3+
id uuid not null default gen_random_uuid (),
4+
anomaly_id bigint not null,
5+
sent_at timestamp with time zone null default now(),
6+
constraint push_anomaly_log_pkey primary key (id),
7+
constraint unique_anomaly_push_log unique (anomaly_id),
8+
constraint push_anomaly_log_anomaly_id_fkey foreign key (anomaly_id) references linked_anomalies (id) on delete cascade
9+
) tablespace pg_default;
10+
11+
-- Enable RLS
12+
alter table public.push_anomaly_log enable row level security;
13+
14+
-- Create policy to allow service role to manage logs
15+
do $$
16+
begin
17+
if not exists (select 1 from pg_policies where tablename = 'push_anomaly_log' and policyname = 'Service role can manage push anomaly logs') then
18+
create policy "Service role can manage push anomaly logs"
19+
on public.push_anomaly_log
20+
for all
21+
using (auth.role() = 'service_role');
22+
end if;
23+
end
24+
$$;
25+
26+
-- Add index for efficient querying
27+
create index if not exists idx_push_anomaly_log_anomaly_id on public.push_anomaly_log (anomaly_id);
28+
create index if not exists idx_push_anomaly_log_sent_at on public.push_anomaly_log (sent_at);

0 commit comments

Comments
 (0)