Skip to content

Commit ab1aa7f

Browse files
committed
✨ feat: implement notficaitons
1 parent 5d77dbe commit ab1aa7f

File tree

11 files changed

+320
-34
lines changed

11 files changed

+320
-34
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
55
PUBLIC_OIDC_AUTHORITY=http://localhost:8080/default/.well-known/openid-configuration
66
PUBLIC_OIDC_CLIENT_ID=default
77

8+
PUBLIC_NTFY_HOST=http://localhost:80
89
NTFY_HOST=http://localhost:80
910

1011
# Please see https://svelte.dev/docs/kit/adapter-node#Environment-variables for details

messages/de.json

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
{
22
"$schema": "https://inlang.com/schema/inlang-message-format",
3-
"connectNotifications": "Benachrichtigungen verbinden",
4-
"connectNotificationsExplaination": "Bitte folge diesen Schritten um Benachrichtigungen für deine Geräte zu aktivieren.",
5-
"downloadTheNTFYApp": "1. Lade die NTFY App herunter."
3+
"logout": "Logout",
4+
"connectNotifications": "Connect notifications",
5+
"connectNotificationsExplaination": "Please follow the below steps to setup notifications for your devices.",
6+
"downloadTheNTFYApp": "1. Download the NTFY app",
7+
"downloadTheNTFYAppExplaination": "To download the NTFY app, follow the below link and select the correct store for your platform.",
8+
"downloadNTFY": "Download NTFY",
9+
"createATopic": "2. Create a topic",
10+
"topicExplaination": "Topics are used to send and receive notifications. They are unique to each user and should be kept secret to you. Everyone with access to your topic can access your notifications which might contain sensitive data! So make sure to keep it to yourself!",
11+
"createYourTopic": "Create your topic",
12+
"copy": "Copy",
13+
"reset": "Reset",
14+
"resetExplaination": "Resetting your topic will make the old one unuseable. If you proceed, you will need to put the new topic in place on all your subscribed devices. You should perform a reset in case your old topic got compromised.",
15+
"confirm": "Confirm",
16+
"cancel": "Cancel",
17+
"notifications": "Notifications",
18+
"copiedToClipboard": "Copied to clipboard!",
19+
"remove": "Remove",
20+
"removeExplaination": "If you proceed, will no longer have a valid topic to receive notifications with. You can do this in case you do not want to be notified anymore.",
21+
"subscribeToTheTopic": "3. Subscribe to the topic",
22+
"subscribeToTheTopicExplaination": "Finally, you have to subcribe to the topic. You can do this on all devices you want to receive notifications on. For instructions on how to do that, see the link below.",
23+
"instructions": "Instructions",
24+
"differentServerExplaination": "When asked if you would like to configure a different server, do that and fill the below value."
625
}

messages/en.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
{
22
"$schema": "https://inlang.com/schema/inlang-message-format",
3+
"logout": "Logout",
34
"connectNotifications": "Connect notifications",
45
"connectNotificationsExplaination": "Please follow the below steps to setup notifications for your devices.",
56
"downloadTheNTFYApp": "1. Download the NTFY app",
67
"downloadTheNTFYAppExplaination": "To download the NTFY app, follow the below link and select the correct store for your platform.",
78
"downloadNTFY": "Download NTFY",
89
"createATopic": "2. Create a topic",
9-
"createATopicExplaination": "Topics are used to send and receive notifications. They are unique to each user and should be kept secret to you. Everyone with ",
1010
"topicExplaination": "Topics are used to send and receive notifications. They are unique to each user and should be kept secret to you. Everyone with access to your topic can access your notifications which might contain sensitive data! So make sure to keep it to yourself!",
1111
"createYourTopic": "Create your topic",
1212
"copy": "Copy",
1313
"reset": "Reset",
1414
"resetExplaination": "Resetting your topic will make the old one unuseable. If you proceed, you will need to put the new topic in place on all your subscribed devices. You should perform a reset in case your old topic got compromised.",
1515
"confirm": "Confirm",
16-
"cancel": "Cancel"
16+
"cancel": "Cancel",
17+
"notifications": "Notifications",
18+
"copiedToClipboard": "Copied to clipboard!",
19+
"remove": "Remove",
20+
"removeExplaination": "If you proceed, will no longer have a valid topic to receive notifications with. You can do this in case you do not want to be notified any longer.",
21+
"subscribeToTheTopic": "3. Subscribe to the topic",
22+
"subscribeToTheTopicExplaination": "Finally, you have to subcribe to the topic. You can do this on all devices you want to receive notifications on. For instructions on how to do that, see the link below.",
23+
"instructions": "Instructions",
24+
"differentServerExplaination": "When asked if you would like to configure a different server, do that and fill the below value"
1725
}

schema.graphql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://
8888
scalar JSON
8989

9090
type Mutation {
91-
todo(id: ID!): User
91+
removeTopic: User
92+
resetTopic: User
9293
}
9394

9495
type Query {

src/api/handlers/user.ts

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import { db } from '$api/db/db';
1+
import { db, schema } from '$api/db/db';
22
import { abilityBuilder, schemaBuilder } from '$api/rumble';
3+
import { configPrivate } from '$config/private';
4+
import { configPublic } from '$config/public';
5+
import { nanoid } from '$lib/helpers/nanoid';
36
import { basics } from './basics';
47

58
const { arg, ref, pubsub, table } = basics('user');
69

7-
abilityBuilder.user.allow('read').when(({ oidc }) => {
10+
abilityBuilder.user.allow(['read', 'update']).when(({ oidc }) => {
811
if (oidc?.user) {
912
return {
1013
where: { id: oidc.user.sub }
@@ -14,20 +17,71 @@ abilityBuilder.user.allow('read').when(({ oidc }) => {
1417

1518
schemaBuilder.mutationFields((t) => {
1619
return {
17-
todo: t.drizzleField({
20+
resetTopic: t.drizzleField({
1821
type: ref,
19-
args: {
20-
id: t.arg.id({ required: true })
21-
},
2222
resolve: async (query, root, args, ctx, info) => {
23-
pubsub.updated(args.id);
23+
const user = ctx.mustBeLoggedIn();
24+
const topicLength =
25+
configPrivate.TOPIC_LENGTH - configPublic.PUBLIC_TOPIC_PREFIX.length - 1;
26+
27+
await db
28+
.update(schema.user)
29+
.set({
30+
ntfyTopic: configPublic.PUBLIC_TOPIC_PREFIX + nanoid(topicLength)
31+
})
32+
.where(
33+
ctx.abilities.user.filter('update', {
34+
inject: {
35+
where: {
36+
id: user.sub
37+
}
38+
}
39+
}).sql.where
40+
);
41+
42+
pubsub.updated(user.sub);
43+
44+
return db.query.user.findFirst(
45+
query(
46+
ctx.abilities.user.filter('read', {
47+
inject: {
48+
where: {
49+
id: user.sub
50+
}
51+
}
52+
}).query.single
53+
)
54+
);
55+
}
56+
}),
57+
removeTopic: t.drizzleField({
58+
type: ref,
59+
resolve: async (query, root, args, ctx, info) => {
60+
const user = ctx.mustBeLoggedIn();
61+
62+
await db
63+
.update(schema.user)
64+
.set({
65+
ntfyTopic: null
66+
})
67+
.where(
68+
ctx.abilities.user.filter('update', {
69+
inject: {
70+
where: {
71+
id: user.sub
72+
}
73+
}
74+
}).sql.where
75+
);
76+
77+
pubsub.updated(user.sub);
2478

2579
return db.query.user.findFirst(
2680
query(
2781
ctx.abilities.user.filter('read', {
2882
inject: {
2983
where: {
30-
id: args.id
84+
id: user.sub
3185
}
3286
}
3387
}).query.single

src/api/services/ntfy.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { configPrivate } from '$config/private';
2+
import { configPublic } from '$config/public';
23

34
export async function sendToNtfyTopic({
45
body,
@@ -9,9 +10,9 @@ export async function sendToNtfyTopic({
910
body: string;
1011
title: string;
1112
}) {
12-
const res = await fetch(`${configPrivate.NTFY_HOST}/${topic}`, {
13-
method: 'POST',
14-
body,
13+
const res = await fetch(configPrivate.NTFY_HOST ?? configPublic.PUBLIC_NTFY_HOST, {
14+
method: 'PUT',
15+
body: JSON.stringify({ message: body, topic }),
1516
headers: {
1617
Title: title
1718
// Priority: 'urgent',

src/lib/config/private.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { env } from '$env/dynamic/private';
22
import { konfigure, sources } from '@m1212e/konfigure';
33
import { Type } from '@sinclair/typebox';
4+
import { configPublic } from './public';
45

56
export const configPrivate = await konfigure({
67
delimeter: 'disabled',
@@ -16,7 +17,9 @@ export const configPrivate = await konfigure({
1617
HOST: Type.Optional(Type.String()),
1718
SMTP_PORT: Type.Number({ default: 3388 }),
1819
SMTP_HOST: Type.String({ default: '0.0.0.0' }),
19-
NTFY_HOST: Type.String()
20+
NTFY_HOST: Type.Optional(Type.String()),
21+
// ntfy.sh max topic length is 65
22+
TOPIC_LENGTH: Type.Integer({ default: 65, minimum: 30 })
2023
}),
2124
sources: [
2225
sources.object(env),
@@ -26,3 +29,13 @@ export const configPrivate = await konfigure({
2629
sources.jsonFile('./config.json')
2730
]
2831
});
32+
33+
const minAmountOfRandomTopicChars = 30;
34+
if (
35+
configPrivate.TOPIC_LENGTH - configPublic.PUBLIC_TOPIC_PREFIX.length <=
36+
minAmountOfRandomTopicChars
37+
) {
38+
throw new Error(
39+
`Your prefix is too long! Please reduce it by ${minAmountOfRandomTopicChars - (configPrivate.TOPIC_LENGTH - configPublic.PUBLIC_TOPIC_PREFIX.length) + 1} chars!`
40+
);
41+
}

src/lib/config/public.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export const configPublic = await konfigure({
1111
PUBLIC_OIDC_CLIENT_ID: Type.String(),
1212
PUBLIC_DEFAULT_LOCALE: Type.String({ default: 'en' }),
1313
PUBLIC_OIDC_LOGIN_CALLBACK_ROUTE: Type.Optional(Type.String()),
14-
PUBLIC_OIDC_LOGOUT_CALLBACK_ROUTE: Type.Optional(Type.String())
14+
PUBLIC_OIDC_LOGOUT_CALLBACK_ROUTE: Type.Optional(Type.String()),
15+
PUBLIC_NTFY_HOST: Type.String({ default: 'https://ntfy.sh' }),
16+
PUBLIC_TOPIC_PREFIX: Type.String({ default: '' })
1517
}),
1618
sources: [sources.object(env)]
1719
});

src/routes/+layout.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
import { locales, localizeHref } from '$lib/paraglide/runtime';
55
import { Toaster } from 'svelte-french-toast';
66
import '../app.css';
7+
import { m } from '$lib/paraglide/messages';
78
89
let { children } = $props();
910
enableViewTransitionApi();
1011
</script>
1112

1213
<svelte:head>
13-
<title>TODO</title>
14+
<title>{m.notifications()}</title>
1415
</svelte:head>
1516

1617
{@render children()}

src/routes/app/+page.gql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
query TopicQuery {
2+
findManyUser {
3+
id
4+
ntfyTopic
5+
}
6+
}

0 commit comments

Comments
 (0)