From b76cef22aefbfb14bfbe023070721240962c0b2d Mon Sep 17 00:00:00 2001 From: Sami Abuzakuk Date: Sun, 12 Oct 2025 14:54:53 +0200 Subject: [PATCH] Add frontend support for notifications --- frontend/src/lib/api.ts | 131 ++++++++++++++++- frontend/src/routes/+layout.svelte | 1 + frontend/src/routes/+page.svelte | 8 ++ .../src/routes/notifications/+page.server.ts | 12 ++ .../src/routes/notifications/+page.svelte | 77 ++++++++++ .../[subscription_id]/+page.server.ts | 18 +++ .../[subscription_id]/+page.svelte | 133 ++++++++++++++++++ frontend/src/routes/settings/+page.svelte | 30 ++-- 8 files changed, 399 insertions(+), 11 deletions(-) create mode 100644 frontend/src/routes/notifications/+page.server.ts create mode 100644 frontend/src/routes/notifications/+page.svelte create mode 100644 frontend/src/routes/notifications/[subscription_id]/+page.server.ts create mode 100644 frontend/src/routes/notifications/[subscription_id]/+page.svelte diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index f37beb5..9174e0b 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1,5 +1,24 @@ export const API_URL = 'http://127.0.0.1:8000'; +/** + * Type definitions for Subscriptions and Notifications + */ +export interface Subscription { + id: number; + topic: string; + created_at: string; +} + +export interface Notification { + id: number; + subscription_id: number; + title: string; + message: string; + priority: number; + created_at: string; + viewed: boolean; +} + /** * Type definitions for Settings */ @@ -8,6 +27,7 @@ export interface Settings { requirements: string; environment: string; user: string; + ntfy_url?: string; } export async function checkHealth(): Promise<'healthy' | 'unhealthy'> { @@ -96,7 +116,7 @@ export async function updateSetting( headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(updatedSetting) + body: JSON.stringify({ ...updatedSetting, ntfy_url: updatedSetting.ntfy_url }) }); if (!response.ok) { throw new Error('Failed to update setting'); @@ -104,6 +124,115 @@ export async function updateSetting( return response.json(); } +// Fetch all subscriptions +export async function fetchSubscriptions(): Promise { + const response = await fetch(`${API_URL}/subscriptions`); + if (!response.ok) { + throw new Error('Failed to fetch subscriptions'); + } + return response.json(); +} + +// Fetch subscriptions by topic +export async function getSubscription(topic_id: string): Promise { + const response = await fetch(`${API_URL}/subscriptions/${topic_id}`); + if (!response.ok) { + throw new Error('Failed to fetch subscriptions'); + } + return response.json(); +} + +// Add a new notification to a subscription +export async function addNotification( + subscriptionId: number, + title: string, + message: string, + priority: number +): Promise { + const response = await fetch(`${API_URL}/notifications`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ subscription_id: subscriptionId, title, message, priority }) + }); + if (!response.ok) { + throw new Error('Failed to add notification'); + } + return response.json(); +} + +// Mark a notification as viewed +export async function setViewed(notificationId: number): Promise { + const response = await fetch(`${API_URL}/notifications/${notificationId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ viewed: true }) + }); + if (!response.ok) { + throw new Error('Failed to set notification as viewed'); + } + return response.json(); +} + +// Add a new subscription +export async function addSubscription(topic: string): Promise { + const response = await fetch(`${API_URL}/subscriptions`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ topic }) + }); + if (!response.ok) { + throw new Error('Failed to add subscription'); + } + return response.json(); +} + +// Delete a subscription +export async function deleteSubscription(subscriptionId: number): Promise { + const response = await fetch(`${API_URL}/subscriptions/${subscriptionId}`, { + method: 'DELETE' + }); + if (!response.ok) { + throw new Error('Failed to delete subscription'); + } +} + +// Get all subscription notifications +export async function fetchSubscriptionNotifications( + subscriptionId: string +): Promise { + const response = await fetch(`${API_URL}/subscriptions/${subscriptionId}/notifications`); + if (!response.ok) { + throw new Error('Failed to fetch subscription notifications'); + } + return response.json(); +} + +// Fetch all notifications or filter by topic +export async function fetchAllNotifications(): Promise { + const url = `${API_URL}/notifications`; + const response = await fetch(url); + if (!response.ok) { + throw new Error('Failed to fetch notifications'); + } + return response.json(); +} + +// Delete a notification +export async function deleteNotification(notificationId: number): Promise { + const response = await fetch(`${API_URL}/notifications/${notificationId}`, { + method: 'DELETE' + }); + if (!response.ok) { + throw new Error('Failed to delete notification'); + } +} + // Fetch a single script by ID export async function fetchScriptById(id: number): Promise + +
+

Subscriptions

+ +
+ {#each subscriptions as subscription (subscription.id)} + +

{subscription.topic}

+
+ {/each} +
+ +
+

Add New Subscription

+
+
+ + +
+ +
+
+
+ + diff --git a/frontend/src/routes/notifications/[subscription_id]/+page.server.ts b/frontend/src/routes/notifications/[subscription_id]/+page.server.ts new file mode 100644 index 0000000..1916f77 --- /dev/null +++ b/frontend/src/routes/notifications/[subscription_id]/+page.server.ts @@ -0,0 +1,18 @@ +import { fetchSubscriptionNotifications, getSubscription } from '$lib/api'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ params }) => { + try { + const subscription_id: string = params.subscription_id; + + const subscription = await getSubscription(subscription_id); + const notifications = (await fetchSubscriptionNotifications(subscription_id)).sort( + (a, b) => new Date(b.created_at!).getTime() - new Date(a.created_at!).getTime() + ); + + return { subscription, notifications }; + } catch (error) { + console.error('Failed to load:', error); + return { subscription: {}, notifications: [] }; + } +}; diff --git a/frontend/src/routes/notifications/[subscription_id]/+page.svelte b/frontend/src/routes/notifications/[subscription_id]/+page.svelte new file mode 100644 index 0000000..2f6fede --- /dev/null +++ b/frontend/src/routes/notifications/[subscription_id]/+page.svelte @@ -0,0 +1,133 @@ + + +
+

Notifications for {data.subscription.topic}:

+ +
+ ← Return to Subscriptions + +
+ + {#if notifications.length === 0} +

No notifications found for this topic.

+ {:else} +
    + {#each notifications as notification (notification.id)} +
  • + + +
  • + {/each} +
+ {/if} + {#if selectedNotification} +
+
+

Notification Details

+
+

Title:

+
{selectedNotification.title}
+
+
+

Message:

+
{selectedNotification.message}
+
+
+

Priority:

+
{selectedNotification.priority}
+
+
+

Created At:

+
{new Date(
+							selectedNotification.created_at
+						).toLocaleString()}
+
+ +
+
+ {/if} +
+ + diff --git a/frontend/src/routes/settings/+page.svelte b/frontend/src/routes/settings/+page.svelte index f43969e..3a32b32 100644 --- a/frontend/src/routes/settings/+page.svelte +++ b/frontend/src/routes/settings/+page.svelte @@ -51,18 +51,28 @@
{#each $settings as setting (setting.id)}
- -
- -
+ - -
- -
+ - - +