kempo 4.4.3 → 4.4.4

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.
Files changed (36) hide show
  1. package/README.md +6 -0
  2. package/dist/admin/nav.fragment.html +9 -0
  3. package/dist/admin/notifications/index.page.html +48 -0
  4. package/dist/kempo/api/notifications/GET.js +2 -0
  5. package/dist/kempo/api/notifications/GET.js.map +1 -0
  6. package/dist/kempo/api/notifications/[id]/DELETE.js +2 -0
  7. package/dist/kempo/api/notifications/[id]/DELETE.js.map +1 -0
  8. package/dist/kempo/api/notifications/[id]/handled/POST.js +2 -0
  9. package/dist/kempo/api/notifications/[id]/handled/POST.js.map +1 -0
  10. package/dist/kempo/api/notifications/[id]/read/POST.js +2 -0
  11. package/dist/kempo/api/notifications/[id]/read/POST.js.map +1 -0
  12. package/dist/kempo/api/notifications/count/GET.js +2 -0
  13. package/dist/kempo/api/notifications/count/GET.js.map +1 -0
  14. package/dist/kempo/api/notifications/read-all/POST.js +2 -0
  15. package/dist/kempo/api/notifications/read-all/POST.js.map +1 -0
  16. package/dist/kempo/components/NotificationBell.js +17 -0
  17. package/dist/kempo/components/NotificationBell.js.map +1 -0
  18. package/dist/kempo/components/NotificationList.js +21 -0
  19. package/dist/kempo/components/NotificationList.js.map +1 -0
  20. package/dist/kempo/icons/info.svg +1 -0
  21. package/dist/kempo/icons/notifications.svg +1 -0
  22. package/dist/kempo/sdk.js +2 -2
  23. package/dist/kempo/sdk.js.map +1 -1
  24. package/package.json +2 -2
  25. package/scripts/init-db.js +1 -0
  26. package/server/db/schema.js +46 -1
  27. package/server/sdk.js +13 -0
  28. package/server/utils/notifications/createNotification.js +143 -0
  29. package/server/utils/notifications/deleteNotification.js +36 -0
  30. package/server/utils/notifications/getNotifications.js +66 -0
  31. package/server/utils/notifications/getUnreadCount.js +26 -0
  32. package/server/utils/notifications/helpers.js +94 -0
  33. package/server/utils/notifications/markAllRead.js +21 -0
  34. package/server/utils/notifications/markHandled.js +39 -0
  35. package/server/utils/notifications/markRead.js +26 -0
  36. package/server/utils/notifications/pruneNotifications.js +34 -0
package/README.md CHANGED
@@ -176,6 +176,8 @@ Kempo provides these tables out of the box:
176
176
  | `setting` | Key-value settings with type, visibility, and ownership |
177
177
  | `realtimeMessage` | Messages on channels that persist, so a reconnecting client can replay what it missed |
178
178
  | `realtimeChannel` | How far each realtime channel has been pruned, to tell a client it missed messages |
179
+ | `notification` | Notifications raised by core or extensions: title, message, level, actions, dedupe key |
180
+ | `notificationRecipient` | One row per person a notification was sent to, with their own read, handled and dismissed state |
179
181
 
180
182
  ## Frontend SDK
181
183
 
@@ -354,6 +356,10 @@ Settings can be managed through the admin panel at `/admin/settings` or the SDK.
354
356
 
355
357
  Extensions can push live updates to browsers: declare a channel, publish to it from any server code, and every subscribed browser on any kempo process receives it. Channels can persist messages so a client that reconnects replays what it missed, and a browser client handles connecting and reconnecting. Browsers can also send to a channel, where the extension's own handler receives and answers it, and extensions observe connections through hooks and act on them through SDK functions, so interactive features (chat, collaboration, games) can be built as extensions without touching a socket. Fast traffic can use an in-memory channel that skips the database, and connections are limited and backpressured. It uses a Postgres `LISTEN`/`NOTIFY` bus, so it needs no extra service. See [docs/realtime.md](docs/realtime.md).
356
358
 
359
+ ## Notifications
360
+
361
+ Core and extensions can notify users with `createNotification`: to specific users, everyone holding a permission, or a group. Each person has their own read state and history, a notification can carry buttons that link somewhere or call an API as the signed-in user, and a `dedupeKey` keeps a repeating event to one notification. The admin has a bell with an unread count and a Notifications page; a public site can use `<k-notification-bell>`. Existing sites need `npx drizzle-kit push` to add the two new tables. See [docs/notifications.md](docs/notifications.md).
362
+
357
363
  ## Email
358
364
 
359
365
  Kempo uses [Resend](https://resend.com/) for sending emails. Set `RESEND_API_KEY` and `EMAIL_FROM` in your `.env` file.
@@ -12,6 +12,9 @@
12
12
  <k-aside-toggle>
13
13
  <h3 class="m0">Kempo Admin</h3>
14
14
  </k-aside-toggle>
15
+ <div class="px pbh">
16
+ <k-notification-bell page-href="/admin/notifications" open-direction="down right"></k-notification-bell>
17
+ </div>
15
18
  <k-aside-item
16
19
  icon="preview"
17
20
  href="/"
@@ -73,6 +76,11 @@
73
76
  href="/admin/extensions"
74
77
  no-expand
75
78
  >Extensions</k-aside-item>
79
+ <k-aside-item
80
+ icon="notifications"
81
+ href="/admin/notifications"
82
+ no-expand
83
+ >Notifications</k-aside-item>
76
84
  <k-aside-item
77
85
  icon="sensors"
78
86
  href="/admin/realtime"
@@ -88,6 +96,7 @@
88
96
  </k-aside>
89
97
  <script type="module">
90
98
  import '/kempo-ui/components/ThemeSwitcher.js';
99
+ import '/kempo/components/NotificationBell.js';
91
100
  const items = document.querySelectorAll('k-aside-item');
92
101
  const currentPath = window.location.pathname;
93
102
 
@@ -0,0 +1,48 @@
1
+ <!--
2
+ locked: true
3
+ -->
4
+ <page title="Notifications - Admin">
5
+ <content>
6
+ <script type="module" src="/kempo-ui/components/Icon.js"></script>
7
+ <script type="module" src="/kempo/components/NotificationList.js"></script>
8
+
9
+ <div class="d-f mb">
10
+ <h1 class="flex mb0">Notifications</h1>
11
+ <div class="btn-grp mr">
12
+ <button id="showAll" class="primary">All</button>
13
+ <button id="showUnread">Unread</button>
14
+ </div>
15
+ <button id="markAllRead"><k-icon name="done_all"></k-icon> Mark all read</button>
16
+ </div>
17
+
18
+ <k-notification-list id="list" paginated></k-notification-list>
19
+
20
+ <p class="small tc-muted">
21
+ Dismissing a notification only removes it from your list. Notifications are deleted for everyone once they are
22
+ older than the retention set by <code>system:notification_retention_days</code> (default 90 days).
23
+ </p>
24
+
25
+ </content>
26
+ <content location="scripts">
27
+ <script type="module">
28
+ import { markAllNotificationsRead } from '/kempo/sdk.js';
29
+
30
+ const list = document.getElementById('list');
31
+ const showAll = document.getElementById('showAll');
32
+ const showUnread = document.getElementById('showUnread');
33
+
34
+ const filter = unreadOnly => {
35
+ list.unreadOnly = unreadOnly;
36
+ showAll.classList.toggle('primary', !unreadOnly);
37
+ showUnread.classList.toggle('primary', unreadOnly);
38
+ };
39
+
40
+ showAll.addEventListener('click', () => filter(false));
41
+ showUnread.addEventListener('click', () => filter(true));
42
+ document.getElementById('markAllRead').addEventListener('click', async () => {
43
+ await markAllNotificationsRead();
44
+ await list.refresh();
45
+ });
46
+ </script>
47
+ </content>
48
+ </page>
@@ -0,0 +1,2 @@
1
+ import t from"../../../../server/utils/auth/getSession.js";import e from"../../../../server/utils/notifications/getNotifications.js";export default async(s,o)=>{const[r,i]=await t({token:s.cookies.session_token});if(r)return o.status(500===r.code?500:401).json({error:500===r.code?r.msg:"Authentication required"});const{unreadOnly:n,limit:u,offset:a}=s.query,[f,c]=await e({userId:i.user.id,unreadOnly:"true"===n,limit:u,offset:a});if(f)return o.status(f.code).json({error:f.msg});o.json(c)};
2
+ //# sourceMappingURL=C:\Users\dusti\dev\kempo-notifications\dist\kempo\api\notifications\GET.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src\\kempo\\api\\notifications\\GET.js","names":["getSession","getNotifications","async","request","response","sessionError","sessionData","token","cookies","session_token","status","code","json","error","msg","unreadOnly","limit","offset","query","result","userId","user","id"],"sources":["0"],"sourcesContent":["import getSession from '../../../../server/utils/auth/getSession.js';\nimport getNotifications from '../../../../server/utils/notifications/getNotifications.js';\n\nexport default async (request, response) => {\n const [sessionError, sessionData] = await getSession({ token: request.cookies.session_token });\n\n if(sessionError){\n return response.status(sessionError.code === 500 ? 500 : 401).json({ error: sessionError.code === 500 ? sessionError.msg : 'Authentication required' });\n }\n\n const { unreadOnly, limit, offset } = request.query;\n const [error, result] = await getNotifications({\n userId: sessionData.user.id,\n unreadOnly: unreadOnly === 'true',\n limit,\n offset\n });\n\n if(error){\n return response.status(error.code).json({ error: error.msg });\n }\n\n response.json(result);\n};\n"],"mappings":"OAAOA,MAAgB,qDAChBC,MAAsB,4EAEdC,MAAOC,EAASC,KAC7B,MAAOC,EAAcC,SAAqBN,EAAW,CAAEO,MAAOJ,EAAQK,QAAQC,gBAE9E,GAAGJ,EACD,OAAOD,EAASM,OAA6B,MAAtBL,EAAaM,KAAe,IAAM,KAAKC,KAAK,CAAEC,MAA6B,MAAtBR,EAAaM,KAAeN,EAAaS,IAAM,4BAG7H,MAAMC,WAAEA,EAAUC,MAAEA,EAAKC,OAAEA,GAAWd,EAAQe,OACvCL,EAAOM,SAAgBlB,EAAiB,CAC7CmB,OAAQd,EAAYe,KAAKC,GACzBP,WAA2B,SAAfA,EACZC,QACAC,WAGF,GAAGJ,EACD,OAAOT,EAASM,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,MAGzDV,EAASQ,KAAKO","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ import t from"../../../../../server/utils/auth/getSession.js";import o from"../../../../../server/utils/notifications/deleteNotification.js";export default async(s,e)=>{const[i,r]=await t({token:s.cookies.session_token});if(i)return e.status(500===i.code?500:401).json({error:500===i.code?i.msg:"Authentication required"});const[n,a]=await o({userId:r.user.id,notificationId:s.params.id});if(n)return e.status(n.code).json({error:n.msg});e.json(a)};
2
+ //# sourceMappingURL=C:\Users\dusti\dev\kempo-notifications\dist\kempo\api\notifications\[id]\DELETE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src\\kempo\\api\\notifications\\[id]\\DELETE.js","names":["getSession","deleteNotification","async","request","response","sessionError","sessionData","token","cookies","session_token","status","code","json","error","msg","result","userId","user","id","notificationId","params"],"sources":["0"],"sourcesContent":["import getSession from '../../../../../server/utils/auth/getSession.js';\nimport deleteNotification from '../../../../../server/utils/notifications/deleteNotification.js';\n\nexport default async (request, response) => {\n const [sessionError, sessionData] = await getSession({ token: request.cookies.session_token });\n\n if(sessionError){\n return response.status(sessionError.code === 500 ? 500 : 401).json({ error: sessionError.code === 500 ? sessionError.msg : 'Authentication required' });\n }\n\n const [error, result] = await deleteNotification({ userId: sessionData.user.id, notificationId: request.params.id });\n\n if(error){\n return response.status(error.code).json({ error: error.msg });\n }\n\n response.json(result);\n};\n"],"mappings":"OAAOA,MAAgB,wDAChBC,MAAwB,iFAEhBC,MAAOC,EAASC,KAC7B,MAAOC,EAAcC,SAAqBN,EAAW,CAAEO,MAAOJ,EAAQK,QAAQC,gBAE9E,GAAGJ,EACD,OAAOD,EAASM,OAA6B,MAAtBL,EAAaM,KAAe,IAAM,KAAKC,KAAK,CAAEC,MAA6B,MAAtBR,EAAaM,KAAeN,EAAaS,IAAM,4BAG7H,MAAOD,EAAOE,SAAgBd,EAAmB,CAAEe,OAAQV,EAAYW,KAAKC,GAAIC,eAAgBhB,EAAQiB,OAAOF,KAE/G,GAAGL,EACD,OAAOT,EAASM,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,MAGzDV,EAASQ,KAAKG","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ import s from"../../../../../../server/utils/auth/getSession.js";import t from"../../../../../../server/utils/notifications/markHandled.js";export default async(o,e)=>{const[r,i]=await s({token:o.cookies.session_token});if(r)return e.status(500===r.code?500:401).json({error:500===r.code?r.msg:"Authentication required"});const[n,a]=await t({userId:i.user.id,notificationId:o.params.id});if(n)return e.status(n.code).json({error:n.msg});e.json(a)};
2
+ //# sourceMappingURL=C:\Users\dusti\dev\kempo-notifications\dist\kempo\api\notifications\[id]\handled\POST.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src\\kempo\\api\\notifications\\[id]\\handled\\POST.js","names":["getSession","markHandled","async","request","response","sessionError","sessionData","token","cookies","session_token","status","code","json","error","msg","result","userId","user","id","notificationId","params"],"sources":["0"],"sourcesContent":["import getSession from '../../../../../../server/utils/auth/getSession.js';\nimport markHandled from '../../../../../../server/utils/notifications/markHandled.js';\n\nexport default async (request, response) => {\n const [sessionError, sessionData] = await getSession({ token: request.cookies.session_token });\n\n if(sessionError){\n return response.status(sessionError.code === 500 ? 500 : 401).json({ error: sessionError.code === 500 ? sessionError.msg : 'Authentication required' });\n }\n\n const [error, result] = await markHandled({ userId: sessionData.user.id, notificationId: request.params.id });\n\n if(error){\n return response.status(error.code).json({ error: error.msg });\n }\n\n response.json(result);\n};\n"],"mappings":"OAAOA,MAAgB,2DAChBC,MAAiB,6EAETC,MAAOC,EAASC,KAC7B,MAAOC,EAAcC,SAAqBN,EAAW,CAAEO,MAAOJ,EAAQK,QAAQC,gBAE9E,GAAGJ,EACD,OAAOD,EAASM,OAA6B,MAAtBL,EAAaM,KAAe,IAAM,KAAKC,KAAK,CAAEC,MAA6B,MAAtBR,EAAaM,KAAeN,EAAaS,IAAM,4BAG7H,MAAOD,EAAOE,SAAgBd,EAAY,CAAEe,OAAQV,EAAYW,KAAKC,GAAIC,eAAgBhB,EAAQiB,OAAOF,KAExG,GAAGL,EACD,OAAOT,EAASM,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,MAGzDV,EAASQ,KAAKG","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ import s from"../../../../../../server/utils/auth/getSession.js";import t from"../../../../../../server/utils/notifications/markRead.js";export default async(o,e)=>{const[r,i]=await s({token:o.cookies.session_token});if(r)return e.status(500===r.code?500:401).json({error:500===r.code?r.msg:"Authentication required"});const[n,a]=await t({userId:i.user.id,notificationId:o.params.id});if(n)return e.status(n.code).json({error:n.msg});e.json(a)};
2
+ //# sourceMappingURL=C:\Users\dusti\dev\kempo-notifications\dist\kempo\api\notifications\[id]\read\POST.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src\\kempo\\api\\notifications\\[id]\\read\\POST.js","names":["getSession","markRead","async","request","response","sessionError","sessionData","token","cookies","session_token","status","code","json","error","msg","result","userId","user","id","notificationId","params"],"sources":["0"],"sourcesContent":["import getSession from '../../../../../../server/utils/auth/getSession.js';\nimport markRead from '../../../../../../server/utils/notifications/markRead.js';\n\nexport default async (request, response) => {\n const [sessionError, sessionData] = await getSession({ token: request.cookies.session_token });\n\n if(sessionError){\n return response.status(sessionError.code === 500 ? 500 : 401).json({ error: sessionError.code === 500 ? sessionError.msg : 'Authentication required' });\n }\n\n const [error, result] = await markRead({ userId: sessionData.user.id, notificationId: request.params.id });\n\n if(error){\n return response.status(error.code).json({ error: error.msg });\n }\n\n response.json(result);\n};\n"],"mappings":"OAAOA,MAAgB,2DAChBC,MAAc,0EAENC,MAAOC,EAASC,KAC7B,MAAOC,EAAcC,SAAqBN,EAAW,CAAEO,MAAOJ,EAAQK,QAAQC,gBAE9E,GAAGJ,EACD,OAAOD,EAASM,OAA6B,MAAtBL,EAAaM,KAAe,IAAM,KAAKC,KAAK,CAAEC,MAA6B,MAAtBR,EAAaM,KAAeN,EAAaS,IAAM,4BAG7H,MAAOD,EAAOE,SAAgBd,EAAS,CAAEe,OAAQV,EAAYW,KAAKC,GAAIC,eAAgBhB,EAAQiB,OAAOF,KAErG,GAAGL,EACD,OAAOT,EAASM,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,MAGzDV,EAASQ,KAAKG","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ import s from"../../../../../server/utils/auth/getSession.js";import t from"../../../../../server/utils/notifications/getUnreadCount.js";export default async(e,o)=>{const[r,i]=await s({token:e.cookies.session_token});if(r)return o.status(500===r.code?500:401).json({error:500===r.code?r.msg:"Authentication required"});const[n,u]=await t({userId:i.user.id});if(n)return o.status(n.code).json({error:n.msg});o.json(u)};
2
+ //# sourceMappingURL=C:\Users\dusti\dev\kempo-notifications\dist\kempo\api\notifications\count\GET.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src\\kempo\\api\\notifications\\count\\GET.js","names":["getSession","getUnreadCount","async","request","response","sessionError","sessionData","token","cookies","session_token","status","code","json","error","msg","result","userId","user","id"],"sources":["0"],"sourcesContent":["import getSession from '../../../../../server/utils/auth/getSession.js';\nimport getUnreadCount from '../../../../../server/utils/notifications/getUnreadCount.js';\n\nexport default async (request, response) => {\n const [sessionError, sessionData] = await getSession({ token: request.cookies.session_token });\n\n if(sessionError){\n return response.status(sessionError.code === 500 ? 500 : 401).json({ error: sessionError.code === 500 ? sessionError.msg : 'Authentication required' });\n }\n\n const [error, result] = await getUnreadCount({ userId: sessionData.user.id });\n\n if(error){\n return response.status(error.code).json({ error: error.msg });\n }\n\n response.json(result);\n};\n"],"mappings":"OAAOA,MAAgB,wDAChBC,MAAoB,6EAEZC,MAAOC,EAASC,KAC7B,MAAOC,EAAcC,SAAqBN,EAAW,CAAEO,MAAOJ,EAAQK,QAAQC,gBAE9E,GAAGJ,EACD,OAAOD,EAASM,OAA6B,MAAtBL,EAAaM,KAAe,IAAM,KAAKC,KAAK,CAAEC,MAA6B,MAAtBR,EAAaM,KAAeN,EAAaS,IAAM,4BAG7H,MAAOD,EAAOE,SAAgBd,EAAe,CAAEe,OAAQV,EAAYW,KAAKC,KAExE,GAAGL,EACD,OAAOT,EAASM,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,MAGzDV,EAASQ,KAAKG","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ import s from"../../../../../server/utils/auth/getSession.js";import e from"../../../../../server/utils/notifications/markAllRead.js";export default async(t,o)=>{const[r,i]=await s({token:t.cookies.session_token});if(r)return o.status(500===r.code?500:401).json({error:500===r.code?r.msg:"Authentication required"});const[n,a]=await e({userId:i.user.id});if(n)return o.status(n.code).json({error:n.msg});o.json(a)};
2
+ //# sourceMappingURL=C:\Users\dusti\dev\kempo-notifications\dist\kempo\api\notifications\read-all\POST.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src\\kempo\\api\\notifications\\read-all\\POST.js","names":["getSession","markAllRead","async","request","response","sessionError","sessionData","token","cookies","session_token","status","code","json","error","msg","result","userId","user","id"],"sources":["0"],"sourcesContent":["import getSession from '../../../../../server/utils/auth/getSession.js';\nimport markAllRead from '../../../../../server/utils/notifications/markAllRead.js';\n\nexport default async (request, response) => {\n const [sessionError, sessionData] = await getSession({ token: request.cookies.session_token });\n\n if(sessionError){\n return response.status(sessionError.code === 500 ? 500 : 401).json({ error: sessionError.code === 500 ? sessionError.msg : 'Authentication required' });\n }\n\n const [error, result] = await markAllRead({ userId: sessionData.user.id });\n\n if(error){\n return response.status(error.code).json({ error: error.msg });\n }\n\n response.json(result);\n};\n"],"mappings":"OAAOA,MAAgB,wDAChBC,MAAiB,0EAETC,MAAOC,EAASC,KAC7B,MAAOC,EAAcC,SAAqBN,EAAW,CAAEO,MAAOJ,EAAQK,QAAQC,gBAE9E,GAAGJ,EACD,OAAOD,EAASM,OAA6B,MAAtBL,EAAaM,KAAe,IAAM,KAAKC,KAAK,CAAEC,MAA6B,MAAtBR,EAAaM,KAAeN,EAAaS,IAAM,4BAG7H,MAAOD,EAAOE,SAAgBd,EAAY,CAAEe,OAAQV,EAAYW,KAAKC,KAErE,GAAGL,EACD,OAAOT,EAASM,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,MAGzDV,EAASQ,KAAKG","ignoreList":[]}
@@ -0,0 +1,17 @@
1
+ import t from"/kempo-ui/components/LightComponent.js";import{html as i,nothing as e}from"/kempo-ui/lit-all.min.js";import"/kempo-ui/components/Dropdown.js";import"/kempo-ui/components/Icon.js";import"./NotificationList.js";import{getUnreadNotificationCount as n,markAllNotificationsRead as s}from"/kempo/sdk.js";export default class o extends t{static properties={pageHref:{type:String,attribute:"page-href"},interval:{type:Number},openDirection:{type:String,attribute:"open-direction"},count:{state:!0},signedOut:{state:!0}};#t=null;constructor(){super(),this.pageHref="",this.interval=6e4,this.openDirection="down right",this.count=0,this.signedOut=!1}connectedCallback(){super.connectedCallback(),window.addEventListener("focus",this.refreshCount),document.addEventListener("visibilitychange",this.handleVisibility),this.refreshCount(),this.interval>0&&(this.#t=setInterval(this.handleTick,this.interval))}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("focus",this.refreshCount),document.removeEventListener("visibilitychange",this.handleVisibility),clearInterval(this.#t)}refreshCount=async()=>{const[t,i]=await n();if(t)return this.signedOut=401===t.code,void(this.signedOut&&(this.count=0));this.signedOut=!1,this.count=i.count};handleTick=()=>{document.hidden||this.refreshCount()};handleVisibility=()=>{document.hidden||this.refreshCount()};handleOpened=t=>{t.target.closest("k-notification-bell")===this&&(this.querySelector("k-notification-list")?.refresh(),this.refreshCount())};handleChange=t=>{this.count=t.detail.unread};markAll=async()=>{await s(),await(this.querySelector("k-notification-list")?.refresh())};renderLightDom(){return this.signedOut?e:i`
2
+ <k-dropdown close-on-select="false" open-direction=${this.openDirection} @opened=${this.handleOpened}>
3
+ <button slot="trigger" class="no-btn d-f" aria-label=${this.count?`Notifications, ${this.count} unread`:"Notifications"}>
4
+ <k-icon name="notifications"></k-icon>
5
+ ${this.count?i`<span class="bg-danger round pxh ml" data-unread-badge>${this.count>99?"99+":this.count}</span>`:e}
6
+ </button>
7
+ <div @notifications-change=${this.handleChange}>
8
+ <div class="d-f p b-bottom">
9
+ <strong class="flex">Notifications</strong>
10
+ <button class="link" ?disabled=${!this.count} @click=${this.markAll}>Mark all read</button>
11
+ </div>
12
+ <k-notification-list compact page-size="8"></k-notification-list>
13
+ ${this.pageHref?i`<div class="ta-center p"><a href=${this.pageHref}>View all</a></div>`:e}
14
+ </div>
15
+ </k-dropdown>
16
+ `}}customElements.define("k-notification-bell",o);
17
+ //# sourceMappingURL=C:\Users\dusti\dev\kempo-notifications\dist\kempo\components\NotificationBell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src\\kempo\\components\\NotificationBell.js","names":["LightComponent","html","nothing","getUnreadNotificationCount","markAllNotificationsRead","NotificationBell","static","pageHref","type","String","attribute","interval","Number","openDirection","count","state","signedOut","timer","constructor","super","this","connectedCallback","window","addEventListener","refreshCount","document","handleVisibility","setInterval","handleTick","disconnectedCallback","removeEventListener","clearInterval","async","error","data","code","hidden","handleOpened","event","target","closest","querySelector","refresh","handleChange","detail","unread","markAll","renderLightDom","customElements","define"],"sources":["0"],"sourcesContent":["import LightComponent from '/kempo-ui/components/LightComponent.js';\nimport { html, nothing } from '/kempo-ui/lit-all.min.js';\nimport '/kempo-ui/components/Dropdown.js';\nimport '/kempo-ui/components/Icon.js';\nimport './NotificationList.js';\nimport { getUnreadNotificationCount, markAllNotificationsRead } from '/kempo/sdk.js';\n\n/*\n A bell with an unread badge that opens the latest notifications. It is the one element a site needs to\n show a signed-in user their notifications; it renders nothing when nobody is signed in.\n\n Attributes: `page-href` (a \"View all\" link to a full notifications page, hidden when unset),\n `interval` (milliseconds between background checks of the count, default 60000, 0 to turn them off) and\n `open-direction` (passed to the dropdown). The count is also refreshed on load, when the window regains\n focus and whenever the panel opens.\n*/\nexport default class NotificationBell extends LightComponent {\n static properties = {\n pageHref: { type: String, attribute: 'page-href' },\n interval: { type: Number },\n openDirection: { type: String, attribute: 'open-direction' },\n count: { state: true },\n signedOut: { state: true }\n };\n\n #timer = null;\n\n constructor(){\n super();\n this.pageHref = '';\n this.interval = 60000;\n this.openDirection = 'down right';\n this.count = 0;\n this.signedOut = false;\n }\n\n /*\n Lifecycle Callbacks\n */\n\n connectedCallback(){\n super.connectedCallback();\n window.addEventListener('focus', this.refreshCount);\n document.addEventListener('visibilitychange', this.handleVisibility);\n this.refreshCount();\n if(this.interval > 0) this.#timer = setInterval(this.handleTick, this.interval);\n }\n\n disconnectedCallback(){\n super.disconnectedCallback();\n window.removeEventListener('focus', this.refreshCount);\n document.removeEventListener('visibilitychange', this.handleVisibility);\n clearInterval(this.#timer);\n }\n\n /*\n Public Methods\n */\n\n refreshCount = async () => {\n const [error, data] = await getUnreadNotificationCount();\n if(error){\n this.signedOut = error.code === 401;\n if(this.signedOut) this.count = 0;\n return;\n }\n this.signedOut = false;\n this.count = data.count;\n };\n\n /*\n Event Handlers\n */\n\n handleTick = () => {\n if(!document.hidden) this.refreshCount();\n };\n\n handleVisibility = () => {\n if(!document.hidden) this.refreshCount();\n };\n\n handleOpened = event => {\n if(event.target.closest('k-notification-bell') !== this) return;\n this.querySelector('k-notification-list')?.refresh();\n this.refreshCount();\n };\n\n handleChange = event => {\n this.count = event.detail.unread;\n };\n\n markAll = async () => {\n await markAllNotificationsRead();\n await this.querySelector('k-notification-list')?.refresh();\n };\n\n /*\n Rendering\n */\n\n renderLightDom(){\n if(this.signedOut) return nothing;\n return html`\n <k-dropdown close-on-select=\"false\" open-direction=${this.openDirection} @opened=${this.handleOpened}>\n <button slot=\"trigger\" class=\"no-btn d-f\" aria-label=${this.count ? `Notifications, ${this.count} unread` : 'Notifications'}>\n <k-icon name=\"notifications\"></k-icon>\n ${this.count ? html`<span class=\"bg-danger round pxh ml\" data-unread-badge>${this.count > 99 ? '99+' : this.count}</span>` : nothing}\n </button>\n <div @notifications-change=${this.handleChange}>\n <div class=\"d-f p b-bottom\">\n <strong class=\"flex\">Notifications</strong>\n <button class=\"link\" ?disabled=${!this.count} @click=${this.markAll}>Mark all read</button>\n </div>\n <k-notification-list compact page-size=\"8\"></k-notification-list>\n ${this.pageHref ? html`<div class=\"ta-center p\"><a href=${this.pageHref}>View all</a></div>` : nothing}\n </div>\n </k-dropdown>\n `;\n }\n}\n\ncustomElements.define('k-notification-bell', NotificationBell);\n"],"mappings":"OAAOA,MAAoB,wDAClBC,aAAMC,MAAe,iCACvB,yCACA,qCACA,6DACEC,8BAA4BC,MAAgC,+BAWtD,MAAMC,UAAyBL,EAC5CM,kBAAoB,CAClBC,SAAU,CAAEC,KAAMC,OAAQC,UAAW,aACrCC,SAAU,CAAEH,KAAMI,QAClBC,cAAe,CAAEL,KAAMC,OAAQC,UAAW,kBAC1CI,MAAO,CAAEC,OAAO,GAChBC,UAAW,CAAED,OAAO,IAGtBE,GAAS,KAET,WAAAC,GACEC,QACAC,KAAKb,SAAW,GAChBa,KAAKT,SAAW,IAChBS,KAAKP,cAAgB,aACrBO,KAAKN,MAAQ,EACbM,KAAKJ,WAAY,CACnB,CAMA,iBAAAK,GACEF,MAAME,oBACNC,OAAOC,iBAAiB,QAASH,KAAKI,cACtCC,SAASF,iBAAiB,mBAAoBH,KAAKM,kBACnDN,KAAKI,eACFJ,KAAKT,SAAW,IAAGS,MAAKH,EAASU,YAAYP,KAAKQ,WAAYR,KAAKT,UACxE,CAEA,oBAAAkB,GACEV,MAAMU,uBACNP,OAAOQ,oBAAoB,QAASV,KAAKI,cACzCC,SAASK,oBAAoB,mBAAoBV,KAAKM,kBACtDK,cAAcX,MAAKH,EACrB,CAMAO,aAAeQ,UACb,MAAOC,EAAOC,SAAc/B,IAC5B,GAAG8B,EAGD,OAFAb,KAAKJ,UAA2B,MAAfiB,EAAME,UACpBf,KAAKJ,YAAWI,KAAKN,MAAQ,IAGlCM,KAAKJ,WAAY,EACjBI,KAAKN,MAAQoB,EAAKpB,OAOpBc,WAAa,KACPH,SAASW,QAAQhB,KAAKI,gBAG5BE,iBAAmB,KACbD,SAASW,QAAQhB,KAAKI,gBAG5Ba,aAAeC,IACVA,EAAMC,OAAOC,QAAQ,yBAA2BpB,OACnDA,KAAKqB,cAAc,wBAAwBC,UAC3CtB,KAAKI,iBAGPmB,aAAeL,IACblB,KAAKN,MAAQwB,EAAMM,OAAOC,QAG5BC,QAAUd,gBACF5B,UACAgB,KAAKqB,cAAc,wBAAwBC,YAOnD,cAAAK,GACE,OAAG3B,KAAKJ,UAAkBd,EACnBD,CAAI;2DAC4CmB,KAAKP,yBAAyBO,KAAKiB;+DAC/BjB,KAAKN,MAAQ,kBAAkBM,KAAKN,eAAiB;;YAExGM,KAAKN,MAAQb,CAAI,0DAA0DmB,KAAKN,MAAQ,GAAK,MAAQM,KAAKN,eAAiBZ;;qCAElGkB,KAAKuB;;;8CAGIvB,KAAKN,gBAAgBM,KAAK0B;;;YAG5D1B,KAAKb,SAAWN,CAAI,oCAAoCmB,KAAKb,8BAAgCL;;;KAIvG,EAGF8C,eAAeC,OAAO,sBAAuB5C","ignoreList":[]}
@@ -0,0 +1,21 @@
1
+ import t from"/kempo-ui/components/LightComponent.js";import{html as i,nothing as e}from"/kempo-ui/lit-all.min.js";import"/kempo-ui/components/Icon.js";import"/kempo-ui/components/Pagination.js";import{getNotifications as s,markNotificationRead as a,markNotificationHandled as n,dismissNotification as o}from"/kempo/sdk.js";const r={info:{icon:"info",color:"tc-primary"},success:{icon:"check_circle",color:"tc-success"},warning:{icon:"warning",color:"tc-warning"},error:{icon:"error",color:"tc-danger"}},c=["POST","PUT","PATCH","DELETE"],l=t=>"string"==typeof t&&/^\/(?![/\\])[^\\\s\u0000-\u001f\u007f]*$/.test(t),d=[["year",31536e3],["month",2592e3],["day",86400],["hour",3600],["minute",60]],h=new Intl.RelativeTimeFormat(void 0,{numeric:"auto"}),u=t=>{const i=(new Date(t).getTime()-Date.now())/1e3,[e,s]=d.find(([,t])=>Math.abs(i)>=t)||["second",1];return Math.abs(i)<45?"just now":h.format(Math.round(i/s),e)};export default class p extends t{static properties={unreadOnly:{type:Boolean,attribute:"unread-only",reflect:!0},pageSize:{type:Number,attribute:"page-size"},paginated:{type:Boolean,reflect:!0},compact:{type:Boolean,reflect:!0},notifications:{state:!0},total:{state:!0},page:{state:!0},loading:{state:!0},failure:{state:!0},busy:{state:!0},rowErrors:{state:!0}};constructor(){super(),this.unreadOnly=!1,this.pageSize=20,this.paginated=!1,this.compact=!1,this.notifications=[],this.total=0,this.page=1,this.loading=!0,this.failure="",this.busy=new Set,this.rowErrors={}}connectedCallback(){super.connectedCallback(),this.refresh()}updated(t){super.updated(t),t.has("unreadOnly")&&void 0!==t.get("unreadOnly")&&(this.page=1,this.refresh())}refresh=async()=>{const[t,i]=await s({unreadOnly:this.unreadOnly,limit:this.pageSize,offset:(this.page-1)*this.pageSize});if(this.loading=!1,t)this.failure=401===t.code?"Sign in to see your notifications.":`Could not load notifications: ${t.msg}`;else{if(!i.notifications.length&&this.page>1)return this.page=Math.max(Math.ceil(i.total/this.pageSize),1),this.refresh();this.failure="",this.notifications=i.notifications,this.total=i.total,this.dispatchEvent(new CustomEvent("notifications-change",{bubbles:!0,detail:{unread:i.unread,total:i.total}}))}};handlePageChange=t=>{t.detail.currentPage!==this.page&&(this.page=t.detail.currentPage,this.refresh())};run=async(t,i)=>{this.busy=new Set(this.busy).add(t),this.rowErrors={...this.rowErrors,[t]:""};const e=await i();e&&(this.rowErrors={...this.rowErrors,[t]:e});const s=new Set(this.busy);s.delete(t),this.busy=s,await this.refresh()};read=t=>this.run(t.id,async()=>(await a(t.id))[0]?.msg);dismiss=t=>this.run(t.id,async()=>(await o(t.id))[0]?.msg);follow=(t,i,e)=>{t.preventDefault(),this.run(i.id,async()=>{await a(i.id),window.location.href=e.href})};call=(t,i)=>this.run(t.id,async()=>{const{method:e,url:s,body:a}=i.api;let o;try{o=await fetch(s,{method:e,credentials:"same-origin",headers:void 0===a?{}:{"Content-Type":"application/json"},body:void 0===a?void 0:JSON.stringify(a)})}catch{return`${i.label} failed: the request could not be sent`}if(!o.ok){const t=await o.json().catch(()=>({}));return`${i.label} failed: ${t.error||t.message||o.status}`}const[r]=await n(t.id);return r?.msg});renderAction=(t,s)=>{const a=this.busy.has(t.id);return void 0!==s.href?l(s.href)?i`<a class="btn mr" href=${s.href} @click=${i=>this.follow(i,t,s)}>${s.label}</a>`:e:s.api&&c.includes(s.api.method)&&l(s.api.url)?i`<button class="primary mr" ?disabled=${a} @click=${()=>this.call(t,s)}>${s.label}</button>`:e};renderItem=t=>{const s=r[t.level]||r.info,a=!t.readAt,n=this.busy.has(t.id);return i`
2
+ <div class="d-f py b-bottom ${this.compact?"px":"pxh"} ${a?"bg-alt":""}" data-notification-id=${t.id} data-unread=${a}>
3
+ <k-icon class="${s.color} mr" name=${s.icon}></k-icon>
4
+ <div class="flex">
5
+ <div><strong class=${a?"":"tc-muted"}>${t.title}</strong> <span class="small tc-muted">${u(t.updatedAt)}</span></div>
6
+ ${t.message?i`<div class="mtq">${t.message}</div>`:e}
7
+ ${t.handledAt?i`<div class="small tc-muted mtq">Done ${u(t.handledAt)}</div>`:t.actions?.length?i`<div class="mth">${t.actions.map(i=>this.renderAction(t,i))}</div>`:e}
8
+ ${this.rowErrors[t.id]?i`<div class="tc-danger mtq" role="alert">${this.rowErrors[t.id]}</div>`:e}
9
+ </div>
10
+ <div class="d-f">
11
+ ${a?i`<button class="no-btn pq" title="Mark as read" aria-label="Mark as read" ?disabled=${n} @click=${()=>this.read(t)}><k-icon name="check"></k-icon></button>`:e}
12
+ <button class="no-btn pq" title="Dismiss" aria-label="Dismiss" ?disabled=${n} @click=${()=>this.dismiss(t)}><k-icon name="close"></k-icon></button>
13
+ </div>
14
+ </div>
15
+ `};renderLightDom(){return this.failure?i`<p class="p tc-muted">${this.failure}</p>`:this.loading?i`<p class="p tc-muted">Loading&hellip;</p>`:this.notifications.length?i`
16
+ <div>${this.notifications.map(this.renderItem)}</div>
17
+ ${this.paginated&&this.total>this.pageSize?i`
18
+ <k-pagination class="d-b my" controls="simple" .page=${this.page} .totalItems=${this.total} .itemsPerPage=${this.pageSize} @page-change=${this.handlePageChange}></k-pagination>
19
+ `:e}
20
+ `:i`<p class="p tc-muted">${this.unreadOnly?"No unread notifications.":"No notifications."}</p>`}}customElements.define("k-notification-list",p);
21
+ //# sourceMappingURL=C:\Users\dusti\dev\kempo-notifications\dist\kempo\components\NotificationList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src\\kempo\\components\\NotificationList.js","names":["LightComponent","html","nothing","getNotifications","markNotificationRead","markNotificationHandled","dismissNotification","LEVELS","info","icon","color","success","warning","error","API_METHODS","isLocalPath","value","test","units","relativeFormat","Intl","RelativeTimeFormat","undefined","numeric","relativeTime","seconds","Date","getTime","now","unit","size","find","Math","abs","format","round","NotificationList","static","unreadOnly","type","Boolean","attribute","reflect","pageSize","Number","paginated","compact","notifications","state","total","page","loading","failure","busy","rowErrors","constructor","super","this","Set","connectedCallback","refresh","updated","changed","has","get","async","data","limit","offset","code","msg","length","max","ceil","dispatchEvent","CustomEvent","bubbles","detail","unread","handlePageChange","event","currentPage","run","id","task","add","delete","read","item","dismiss","follow","action","preventDefault","window","location","href","call","method","url","body","api","response","fetch","credentials","headers","JSON","stringify","label","ok","json","catch","message","status","renderAction","disabled","includes","renderItem","level","readAt","title","updatedAt","handledAt","actions","map","renderLightDom","customElements","define"],"sources":["0"],"sourcesContent":["import LightComponent from '/kempo-ui/components/LightComponent.js';\nimport { html, nothing } from '/kempo-ui/lit-all.min.js';\nimport '/kempo-ui/components/Icon.js';\nimport '/kempo-ui/components/Pagination.js';\nimport { getNotifications, markNotificationRead, markNotificationHandled, dismissNotification } from '/kempo/sdk.js';\n\n/*\n Presentation of a notification's level\n*/\n\nconst LEVELS = {\n info: { icon: 'info', color: 'tc-primary' },\n success: { icon: 'check_circle', color: 'tc-success' },\n warning: { icon: 'warning', color: 'tc-warning' },\n error: { icon: 'error', color: 'tc-danger' }\n};\n\nconst API_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE'];\n\n/*\n The server refuses anything else when a notification is created. It is checked again here because the\n list renders whatever the API returns, and a link that is not a path on this site must never be followed.\n*/\nconst isLocalPath = value => typeof value === 'string' && /^\\/(?![/\\\\])[^\\\\\\s\\u0000-\\u001f\\u007f]*$/.test(value);\n\nconst units = [['year', 31536000], ['month', 2592000], ['day', 86400], ['hour', 3600], ['minute', 60]];\nconst relativeFormat = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });\n\nconst relativeTime = value => {\n const seconds = (new Date(value).getTime() - Date.now()) / 1000;\n const [unit, size] = units.find(([, size]) => Math.abs(seconds) >= size) || ['second', 1];\n return Math.abs(seconds) < 45 ? 'just now' : relativeFormat.format(Math.round(seconds / size), unit);\n};\n\n/*\n Renders one signed-in user's notifications: level icon, title, message, relative time, the actions the\n sender attached, and controls to mark one read or dismiss it. Title and message are rendered as text,\n never as HTML.\n\n Attributes: `unread-only`, `page-size` (default 20), `paginated` (adds page controls), `compact`\n (tighter rows for a dropdown). Call `refresh()` to reload. Fires `notifications-change` with\n `{ unread, total }` after every load or change, which is how a bell keeps its badge current.\n*/\nexport default class NotificationList extends LightComponent {\n static properties = {\n unreadOnly: { type: Boolean, attribute: 'unread-only', reflect: true },\n pageSize: { type: Number, attribute: 'page-size' },\n paginated: { type: Boolean, reflect: true },\n compact: { type: Boolean, reflect: true },\n notifications: { state: true },\n total: { state: true },\n page: { state: true },\n loading: { state: true },\n failure: { state: true },\n busy: { state: true },\n rowErrors: { state: true }\n };\n\n constructor(){\n super();\n this.unreadOnly = false;\n this.pageSize = 20;\n this.paginated = false;\n this.compact = false;\n this.notifications = [];\n this.total = 0;\n this.page = 1;\n this.loading = true;\n this.failure = '';\n this.busy = new Set();\n this.rowErrors = {};\n }\n\n /*\n Lifecycle Callbacks\n */\n\n connectedCallback(){\n super.connectedCallback();\n this.refresh();\n }\n\n updated(changed){\n super.updated(changed);\n if(changed.has('unreadOnly') && changed.get('unreadOnly') !== undefined){\n this.page = 1;\n this.refresh();\n }\n }\n\n /*\n Public Methods\n */\n\n refresh = async () => {\n const [error, data] = await getNotifications({ unreadOnly: this.unreadOnly, limit: this.pageSize, offset: (this.page - 1) * this.pageSize });\n this.loading = false;\n\n if(error){\n this.failure = error.code === 401 ? 'Sign in to see your notifications.' : `Could not load notifications: ${error.msg}`;\n return;\n }\n\n if(!data.notifications.length && this.page > 1){\n this.page = Math.max(Math.ceil(data.total / this.pageSize), 1);\n return this.refresh();\n }\n\n this.failure = '';\n this.notifications = data.notifications;\n this.total = data.total;\n this.dispatchEvent(new CustomEvent('notifications-change', { bubbles: true, detail: { unread: data.unread, total: data.total } }));\n };\n\n /*\n Event Handlers\n */\n\n handlePageChange = event => {\n if(event.detail.currentPage === this.page) return;\n this.page = event.detail.currentPage;\n this.refresh();\n };\n\n run = async (id, task) => {\n this.busy = new Set(this.busy).add(id);\n this.rowErrors = { ...this.rowErrors, [id]: '' };\n const error = await task();\n if(error) this.rowErrors = { ...this.rowErrors, [id]: error };\n const busy = new Set(this.busy);\n busy.delete(id);\n this.busy = busy;\n await this.refresh();\n };\n\n read = item => this.run(item.id, async () => (await markNotificationRead(item.id))[0]?.msg);\n\n dismiss = item => this.run(item.id, async () => (await dismissNotification(item.id))[0]?.msg);\n\n follow = (event, item, action) => {\n event.preventDefault();\n this.run(item.id, async () => {\n await markNotificationRead(item.id);\n window.location.href = action.href;\n });\n };\n\n /*\n The request is made by this browser with the signed-in user's own session, so the endpoint it names\n applies its own permission checks to that user. Only a response that succeeded marks it handled.\n */\n call = (item, action) => this.run(item.id, async () => {\n const { method, url, body } = action.api;\n let response;\n try {\n response = await fetch(url, {\n method,\n credentials: 'same-origin',\n headers: body === undefined ? {} : { 'Content-Type': 'application/json' },\n body: body === undefined ? undefined : JSON.stringify(body)\n });\n } catch {\n return `${action.label} failed: the request could not be sent`;\n }\n if(!response.ok){\n const detail = await response.json().catch(() => ({}));\n return `${action.label} failed: ${detail.error || detail.message || response.status}`;\n }\n const [error] = await markNotificationHandled(item.id);\n return error?.msg;\n });\n\n /*\n Rendering\n */\n\n renderAction = (item, action) => {\n const disabled = this.busy.has(item.id);\n if(action.href !== undefined){\n return isLocalPath(action.href)\n ? html`<a class=\"btn mr\" href=${action.href} @click=${event => this.follow(event, item, action)}>${action.label}</a>`\n : nothing;\n }\n if(action.api && API_METHODS.includes(action.api.method) && isLocalPath(action.api.url)){\n return html`<button class=\"primary mr\" ?disabled=${disabled} @click=${() => this.call(item, action)}>${action.label}</button>`;\n }\n return nothing;\n };\n\n renderItem = item => {\n const level = LEVELS[item.level] || LEVELS.info;\n const unread = !item.readAt;\n const disabled = this.busy.has(item.id);\n return html`\n <div class=\"d-f py b-bottom ${this.compact ? 'px' : 'pxh'} ${unread ? 'bg-alt' : ''}\" data-notification-id=${item.id} data-unread=${unread}>\n <k-icon class=\"${level.color} mr\" name=${level.icon}></k-icon>\n <div class=\"flex\">\n <div><strong class=${unread ? '' : 'tc-muted'}>${item.title}</strong> <span class=\"small tc-muted\">${relativeTime(item.updatedAt)}</span></div>\n ${item.message ? html`<div class=\"mtq\">${item.message}</div>` : nothing}\n ${item.handledAt\n ? html`<div class=\"small tc-muted mtq\">Done ${relativeTime(item.handledAt)}</div>`\n : item.actions?.length ? html`<div class=\"mth\">${item.actions.map(action => this.renderAction(item, action))}</div>` : nothing}\n ${this.rowErrors[item.id] ? html`<div class=\"tc-danger mtq\" role=\"alert\">${this.rowErrors[item.id]}</div>` : nothing}\n </div>\n <div class=\"d-f\">\n ${unread ? html`<button class=\"no-btn pq\" title=\"Mark as read\" aria-label=\"Mark as read\" ?disabled=${disabled} @click=${() => this.read(item)}><k-icon name=\"check\"></k-icon></button>` : nothing}\n <button class=\"no-btn pq\" title=\"Dismiss\" aria-label=\"Dismiss\" ?disabled=${disabled} @click=${() => this.dismiss(item)}><k-icon name=\"close\"></k-icon></button>\n </div>\n </div>\n `;\n };\n\n renderLightDom(){\n if(this.failure) return html`<p class=\"p tc-muted\">${this.failure}</p>`;\n if(this.loading) return html`<p class=\"p tc-muted\">Loading&hellip;</p>`;\n if(!this.notifications.length) return html`<p class=\"p tc-muted\">${this.unreadOnly ? 'No unread notifications.' : 'No notifications.'}</p>`;\n return html`\n <div>${this.notifications.map(this.renderItem)}</div>\n ${this.paginated && this.total > this.pageSize ? html`\n <k-pagination class=\"d-b my\" controls=\"simple\" .page=${this.page} .totalItems=${this.total} .itemsPerPage=${this.pageSize} @page-change=${this.handlePageChange}></k-pagination>\n ` : nothing}\n `;\n }\n}\n\ncustomElements.define('k-notification-list', NotificationList);\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M440-280h80v-240h-80v240Zm68.5-331.5Q520-623 520-640t-11.5-28.5Q497-680 480-680t-28.5 11.5Q440-657 440-640t11.5 28.5Q463-600 480-600t28.5-11.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M160-200v-80h80v-280q0-83 50-147.5T420-792v-28q0-25 17.5-42.5T480-880q25 0 42.5 17.5T540-820v28q80 20 130 84.5T720-560v280h80v80H160Zm320-300Zm0 420q-33 0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80ZM320-280h320v-280q0-66-47-113t-113-47q-66 0-113 47t-47 113v280Z"/></svg>
package/dist/kempo/sdk.js CHANGED
@@ -1,2 +1,2 @@
1
- import{get as e,post as s,put as o,patch as n,del as t}from"./fetch.js";const r="/kempo/api";export const register=async({email:e,password:o,name:n})=>s(`${r}/auth/register/email`,{email:e,password:o,name:n});export const login=async({email:e,password:o})=>s(`${r}/auth/login/email`,{email:e,password:o});export const logout=async()=>s(`${r}/auth/logout`,{});export const getSession=async()=>e(`${r}/auth/session`);export const forgotPassword=async({email:e})=>s(`${r}/forgot-password`,{email:e});export const resetPassword=async({token:e,password:o,logoutAll:n=!1})=>s(`${r}/auth/reset-password`,{token:e,newPassword:o,logoutAll:n});export const changePassword=async({currentPassword:e,newPassword:o,logoutAll:n})=>s(`${r}/auth/change-password`,{currentPassword:e,newPassword:o,logoutAll:n});export const verifyEmail=async({token:e})=>s(`${r}/auth/verify-email`,{token:e});export const currentUserHasPermission=async({permission:s})=>e(`${r}/user/current/permissions`,{permission:s});export const currentUserHasAllPermissions=async({permissions:s})=>e(`${r}/user/current/permissions/all`,{permissions:s});export const currentUserHasSomePermissions=async({permissions:s})=>e(`${r}/user/current/permissions/some`,{permissions:s});export const getAllCurrentUserPermissions=async()=>e(`${r}/user/current/permissions/list`);export const userHasPermission=async({userid:s,permission:o})=>e(`${r}/user/${s}/permissions`,{permission:o});export const userHasAllPermissions=async({userid:s,permissions:o})=>e(`${r}/user/${s}/permissions/all`,{permissions:o});export const userHasSomePermissions=async({userid:s,permissions:o})=>e(`${r}/user/${s}/permissions/some`,{permissions:o});export const getAllUserPermissions=async({userid:s})=>e(`${r}/user/${s}/permissions/list`);export const getUsers=async({limit:s,offset:o}={})=>e(`${r}/user`,{limit:s,offset:o});export const searchUsers=async({q:s,limit:o,offset:n,inGroup:t,notInGroup:a}={})=>e(`${r}/user/search`,{q:s,limit:o,offset:n,inGroup:t,notInGroup:a});export const createUser=async({name:e,email:o,password:n,emailVerified:t})=>s(`${r}/user/create`,{name:e,email:o,password:n,emailVerified:t});export const deleteUsers=async e=>t(`${r}/user`,{ids:e});export const updateUser=async({id:e,name:s,email:o,emailVerified:t,createdAt:a})=>n(`${r}/user`,{id:e,name:s,email:o,emailVerified:t,createdAt:a});export const getUser=async s=>e(`${r}/user/${s}`);export const getUserSessions=async(s,{limit:o,offset:n}={})=>e(`${r}/user/${s}/sessions`,{limit:o,offset:n});export const deleteUserSession=async(e,s)=>t(`${r}/user/${e}/sessions`,{sessionToken:s});export const deleteExpiredUserSessions=async e=>t(`${r}/user/${e}/sessions/expired`);export const getUserGroups=async(s,{limit:o,offset:n}={})=>e(`${r}/user/${s}/groups`,{limit:o,offset:n});export const addUserToGroup=async(e,o)=>s(`${r}/user/${e}/groups`,{groupName:o});export const removeUserFromGroup=async(e,s)=>t(`${r}/user/${e}/groups`,{groupName:s});export const listGroups=async({limit:s,offset:o,owner:n}={})=>e(`${r}/groups`,{limit:s,offset:o,owner:n});export const createGroup=async({name:e,description:o})=>s(`${r}/groups`,{name:e,description:o});export const updateGroup=async({name:e,newName:s,description:o})=>n(`${r}/groups`,{name:e,newName:s,description:o});export const deleteGroups=async e=>t(`${r}/groups`,{names:e});export const getGroup=async s=>e(`${r}/groups/${encodeURIComponent(s)}`);export const getGroupMembers=async(s,{limit:o,offset:n}={})=>e(`${r}/groups/${encodeURIComponent(s)}/members`,{limit:o,offset:n});export const addMemberToGroup=async(e,o)=>s(`${r}/groups/${encodeURIComponent(e)}/members`,{userId:o});export const removeMemberFromGroup=async(e,s)=>t(`${r}/groups/${encodeURIComponent(e)}/members`,{userId:s});export const getGroupPermissions=async s=>e(`${r}/groups/${encodeURIComponent(s)}/permissions`);export const addPermissionToGroup=async(e,o)=>s(`${r}/groups/${encodeURIComponent(e)}/permissions`,{permissionName:o});export const removePermissionFromGroup=async(e,s)=>t(`${r}/groups/${encodeURIComponent(e)}/permissions`,{permissionName:s});export const listPermissions=async({limit:s,offset:o,owner:n}={})=>e(`${r}/permissions`,{limit:s,offset:o,owner:n});export const createPermissions=async e=>s(`${r}/permissions`,{permissions:e});export const updatePermission=async({name:e,description:s})=>n(`${r}/permissions`,{name:e,description:s});export const deletePermissions=async e=>t(`${r}/permissions`,{names:e});export const getSetting=async(s,o)=>e(`${r}/settings/${encodeURIComponent(s)}/${encodeURIComponent(o)}`);export const listSettings=async({owner:s,limit:o,offset:n}={})=>e(`${r}/settings/${encodeURIComponent(s)}`,{limit:o,offset:n});export const setSetting=async(e,o,n)=>s(`${r}/settings/${encodeURIComponent(e)}/${encodeURIComponent(o)}`,n);export const deleteSetting=async(e,s)=>t(`${r}/settings/${encodeURIComponent(e)}/${encodeURIComponent(s)}`);export const listPages=async({owner:s,showHidden:o,offset:n,limit:t}={})=>e(`${r}/pages`,{owner:s,showHidden:o,offset:n,limit:t});export const rescanPages=async()=>s(`${r}/pages/rescan`,{});export const listTemplates=async()=>e(`${r}/templates`);export const listDirectories=async()=>e(`${r}/pages/directories`);export const getPage=async s=>e(`${r}/pages/file`,{file:s});export const createPage=async({directory:e,name:o,template:n})=>s(`${r}/pages`,{directory:e,name:o,template:n});export const updatePage=async({file:e,name:s,title:n,description:t,author:a,template:i,contents:c,extraMetadata:l})=>o(`${r}/pages/file`,{file:e,name:s,title:n,description:t,author:a,template:i,contents:c,extraMetadata:l});export const deletePages=async e=>t(`${r}/pages`,{files:e});export const disablePage=async e=>o(`${r}/pages/disable`,{file:e});export const enablePage=async e=>o(`${r}/pages/enable`,{file:e});export const movePage=async({file:e,newFile:s})=>n(`${r}/pages/file`,{file:e,newFile:s});export const searchPagesByMetadata=async s=>e(`${r}/pages/search`,{query:JSON.stringify(s)});export const setPageLocked=async({file:e,locked:s})=>o(`${r}/pages/lock`,{file:e,locked:s});export const getTemplate=async s=>e(`${r}/templates/file`,{file:s});export const createTemplate=async({directory:e,name:o,copyFrom:n})=>s(`${r}/templates`,{directory:e,name:o,copyFrom:n});export const updateTemplate=async({file:e,name:s,author:n,markup:t})=>o(`${r}/templates/file`,{file:e,name:s,author:n,markup:t});export const deleteTemplates=async e=>t(`${r}/templates`,{files:e});export const disableTemplate=async e=>o(`${r}/templates/disable`,{file:e});export const enableTemplate=async e=>o(`${r}/templates/enable`,{file:e});export const moveTemplate=async({file:e,newFile:s})=>n(`${r}/templates/file`,{file:e,newFile:s});export const setTemplateLocked=async({file:e,locked:s})=>o(`${r}/templates/lock`,{file:e,locked:s});export const listFragments=async()=>e(`${r}/fragments`);export const getFragment=async s=>e(`${r}/fragments/file`,{file:s});export const createFragment=async({directory:e,name:o})=>s(`${r}/fragments`,{directory:e,name:o});export const updateFragment=async({file:e,author:s,markup:n})=>o(`${r}/fragments/file`,{file:e,author:s,markup:n});export const deleteFragments=async e=>t(`${r}/fragments`,{files:e});export const moveFragment=async({file:e,newFile:s})=>n(`${r}/fragments/file`,{file:e,newFile:s});export const disableFragment=async e=>o(`${r}/fragments/disable`,{file:e});export const enableFragment=async e=>o(`${r}/fragments/enable`,{file:e});export const setFragmentLocked=async({file:e,locked:s})=>o(`${r}/fragments/lock`,{file:e,locked:s});export const listGlobalContent=async()=>e(`${r}/globals`);export const getGlobalContent=async s=>e(`${r}/globals/entry`,{id:s});export const createGlobalContent=async({name:e,location:o,priority:n})=>s(`${r}/globals`,{name:e,location:o,priority:n});export const updateGlobalContent=async({id:e,name:s,location:n,priority:t,markup:a})=>o(`${r}/globals/entry`,{id:e,name:s,location:n,priority:t,markup:a});export const deleteGlobalContent=async e=>t(`${r}/globals`,{ids:e});export const disableGlobalContent=async e=>o(`${r}/globals/disable`,{id:e});export const enableGlobalContent=async e=>o(`${r}/globals/enable`,{id:e});export const setGlobalContentLocked=async({id:e,locked:s})=>o(`${r}/globals/lock`,{id:e,locked:s});export const listExtensions=async({limit:s,offset:o}={})=>e(`${r}/extensions`,{limit:s,offset:o});export const listAvailableExtensions=async()=>e(`${r}/extensions/available`);export const listKnownExtensions=async()=>e(`${r}/extensions/known`);export const listRealtimeConnections=async()=>e(`${r}/realtime/connections`);export const installExtension=async e=>s(`${r}/extensions`,{name:e});export const uninstallExtension=async(e,s=!1)=>t(`${r}/extensions`,{name:e,purgeData:s});export const enableExtension=async e=>s(`${r}/extensions/${encodeURIComponent(e)}/enable`,{});export const disableExtension=async e=>s(`${r}/extensions/${encodeURIComponent(e)}/disable`,{});export const listAdminGlobalContent=async()=>e(`${r}/admin-globals`);export const getAdminGlobalContent=async s=>e(`${r}/admin-globals/${encodeURIComponent(s)}`);export const createAdminGlobalContent=async({name:e,location:o,priority:n,owner:t,markup:a})=>s(`${r}/admin-globals`,{name:e,location:o,priority:n,owner:t,markup:a});export const updateAdminGlobalContent=async(e,{name:s,location:n,priority:t,markup:a})=>o(`${r}/admin-globals/${encodeURIComponent(e)}`,{name:s,location:n,priority:t,markup:a});export const deleteAdminGlobalContent=async e=>t(`${r}/admin-globals`,{ids:e});export const enableAdminGlobalContent=async e=>o(`${r}/admin-globals/${encodeURIComponent(e)}/enable`,{});export const disableAdminGlobalContent=async e=>o(`${r}/admin-globals/${encodeURIComponent(e)}/disable`,{});
2
- //# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\kempo\sdk.js.map
1
+ import{get as e,post as s,put as o,patch as n,del as t}from"./fetch.js";const a="/kempo/api";export const register=async({email:e,password:o,name:n})=>s(`${a}/auth/register/email`,{email:e,password:o,name:n});export const login=async({email:e,password:o})=>s(`${a}/auth/login/email`,{email:e,password:o});export const logout=async()=>s(`${a}/auth/logout`,{});export const getSession=async()=>e(`${a}/auth/session`);export const forgotPassword=async({email:e})=>s(`${a}/forgot-password`,{email:e});export const resetPassword=async({token:e,password:o,logoutAll:n=!1})=>s(`${a}/auth/reset-password`,{token:e,newPassword:o,logoutAll:n});export const changePassword=async({currentPassword:e,newPassword:o,logoutAll:n})=>s(`${a}/auth/change-password`,{currentPassword:e,newPassword:o,logoutAll:n});export const verifyEmail=async({token:e})=>s(`${a}/auth/verify-email`,{token:e});export const currentUserHasPermission=async({permission:s})=>e(`${a}/user/current/permissions`,{permission:s});export const currentUserHasAllPermissions=async({permissions:s})=>e(`${a}/user/current/permissions/all`,{permissions:s});export const currentUserHasSomePermissions=async({permissions:s})=>e(`${a}/user/current/permissions/some`,{permissions:s});export const getAllCurrentUserPermissions=async()=>e(`${a}/user/current/permissions/list`);export const userHasPermission=async({userid:s,permission:o})=>e(`${a}/user/${s}/permissions`,{permission:o});export const userHasAllPermissions=async({userid:s,permissions:o})=>e(`${a}/user/${s}/permissions/all`,{permissions:o});export const userHasSomePermissions=async({userid:s,permissions:o})=>e(`${a}/user/${s}/permissions/some`,{permissions:o});export const getAllUserPermissions=async({userid:s})=>e(`${a}/user/${s}/permissions/list`);export const getUsers=async({limit:s,offset:o}={})=>e(`${a}/user`,{limit:s,offset:o});export const searchUsers=async({q:s,limit:o,offset:n,inGroup:t,notInGroup:r}={})=>e(`${a}/user/search`,{q:s,limit:o,offset:n,inGroup:t,notInGroup:r});export const createUser=async({name:e,email:o,password:n,emailVerified:t})=>s(`${a}/user/create`,{name:e,email:o,password:n,emailVerified:t});export const deleteUsers=async e=>t(`${a}/user`,{ids:e});export const updateUser=async({id:e,name:s,email:o,emailVerified:t,createdAt:r})=>n(`${a}/user`,{id:e,name:s,email:o,emailVerified:t,createdAt:r});export const getUser=async s=>e(`${a}/user/${s}`);export const getUserSessions=async(s,{limit:o,offset:n}={})=>e(`${a}/user/${s}/sessions`,{limit:o,offset:n});export const deleteUserSession=async(e,s)=>t(`${a}/user/${e}/sessions`,{sessionToken:s});export const deleteExpiredUserSessions=async e=>t(`${a}/user/${e}/sessions/expired`);export const getUserGroups=async(s,{limit:o,offset:n}={})=>e(`${a}/user/${s}/groups`,{limit:o,offset:n});export const addUserToGroup=async(e,o)=>s(`${a}/user/${e}/groups`,{groupName:o});export const removeUserFromGroup=async(e,s)=>t(`${a}/user/${e}/groups`,{groupName:s});export const listGroups=async({limit:s,offset:o,owner:n}={})=>e(`${a}/groups`,{limit:s,offset:o,owner:n});export const createGroup=async({name:e,description:o})=>s(`${a}/groups`,{name:e,description:o});export const updateGroup=async({name:e,newName:s,description:o})=>n(`${a}/groups`,{name:e,newName:s,description:o});export const deleteGroups=async e=>t(`${a}/groups`,{names:e});export const getGroup=async s=>e(`${a}/groups/${encodeURIComponent(s)}`);export const getGroupMembers=async(s,{limit:o,offset:n}={})=>e(`${a}/groups/${encodeURIComponent(s)}/members`,{limit:o,offset:n});export const addMemberToGroup=async(e,o)=>s(`${a}/groups/${encodeURIComponent(e)}/members`,{userId:o});export const removeMemberFromGroup=async(e,s)=>t(`${a}/groups/${encodeURIComponent(e)}/members`,{userId:s});export const getGroupPermissions=async s=>e(`${a}/groups/${encodeURIComponent(s)}/permissions`);export const addPermissionToGroup=async(e,o)=>s(`${a}/groups/${encodeURIComponent(e)}/permissions`,{permissionName:o});export const removePermissionFromGroup=async(e,s)=>t(`${a}/groups/${encodeURIComponent(e)}/permissions`,{permissionName:s});export const listPermissions=async({limit:s,offset:o,owner:n}={})=>e(`${a}/permissions`,{limit:s,offset:o,owner:n});export const createPermissions=async e=>s(`${a}/permissions`,{permissions:e});export const updatePermission=async({name:e,description:s})=>n(`${a}/permissions`,{name:e,description:s});export const deletePermissions=async e=>t(`${a}/permissions`,{names:e});export const getSetting=async(s,o)=>e(`${a}/settings/${encodeURIComponent(s)}/${encodeURIComponent(o)}`);export const listSettings=async({owner:s,limit:o,offset:n}={})=>e(`${a}/settings/${encodeURIComponent(s)}`,{limit:o,offset:n});export const setSetting=async(e,o,n)=>s(`${a}/settings/${encodeURIComponent(e)}/${encodeURIComponent(o)}`,n);export const deleteSetting=async(e,s)=>t(`${a}/settings/${encodeURIComponent(e)}/${encodeURIComponent(s)}`);export const listPages=async({owner:s,showHidden:o,offset:n,limit:t}={})=>e(`${a}/pages`,{owner:s,showHidden:o,offset:n,limit:t});export const rescanPages=async()=>s(`${a}/pages/rescan`,{});export const listTemplates=async()=>e(`${a}/templates`);export const listDirectories=async()=>e(`${a}/pages/directories`);export const getPage=async s=>e(`${a}/pages/file`,{file:s});export const createPage=async({directory:e,name:o,template:n})=>s(`${a}/pages`,{directory:e,name:o,template:n});export const updatePage=async({file:e,name:s,title:n,description:t,author:r,template:i,contents:c,extraMetadata:l})=>o(`${a}/pages/file`,{file:e,name:s,title:n,description:t,author:r,template:i,contents:c,extraMetadata:l});export const deletePages=async e=>t(`${a}/pages`,{files:e});export const disablePage=async e=>o(`${a}/pages/disable`,{file:e});export const enablePage=async e=>o(`${a}/pages/enable`,{file:e});export const movePage=async({file:e,newFile:s})=>n(`${a}/pages/file`,{file:e,newFile:s});export const searchPagesByMetadata=async s=>e(`${a}/pages/search`,{query:JSON.stringify(s)});export const setPageLocked=async({file:e,locked:s})=>o(`${a}/pages/lock`,{file:e,locked:s});export const getTemplate=async s=>e(`${a}/templates/file`,{file:s});export const createTemplate=async({directory:e,name:o,copyFrom:n})=>s(`${a}/templates`,{directory:e,name:o,copyFrom:n});export const updateTemplate=async({file:e,name:s,author:n,markup:t})=>o(`${a}/templates/file`,{file:e,name:s,author:n,markup:t});export const deleteTemplates=async e=>t(`${a}/templates`,{files:e});export const disableTemplate=async e=>o(`${a}/templates/disable`,{file:e});export const enableTemplate=async e=>o(`${a}/templates/enable`,{file:e});export const moveTemplate=async({file:e,newFile:s})=>n(`${a}/templates/file`,{file:e,newFile:s});export const setTemplateLocked=async({file:e,locked:s})=>o(`${a}/templates/lock`,{file:e,locked:s});export const listFragments=async()=>e(`${a}/fragments`);export const getFragment=async s=>e(`${a}/fragments/file`,{file:s});export const createFragment=async({directory:e,name:o})=>s(`${a}/fragments`,{directory:e,name:o});export const updateFragment=async({file:e,author:s,markup:n})=>o(`${a}/fragments/file`,{file:e,author:s,markup:n});export const deleteFragments=async e=>t(`${a}/fragments`,{files:e});export const moveFragment=async({file:e,newFile:s})=>n(`${a}/fragments/file`,{file:e,newFile:s});export const disableFragment=async e=>o(`${a}/fragments/disable`,{file:e});export const enableFragment=async e=>o(`${a}/fragments/enable`,{file:e});export const setFragmentLocked=async({file:e,locked:s})=>o(`${a}/fragments/lock`,{file:e,locked:s});export const listGlobalContent=async()=>e(`${a}/globals`);export const getGlobalContent=async s=>e(`${a}/globals/entry`,{id:s});export const createGlobalContent=async({name:e,location:o,priority:n})=>s(`${a}/globals`,{name:e,location:o,priority:n});export const updateGlobalContent=async({id:e,name:s,location:n,priority:t,markup:r})=>o(`${a}/globals/entry`,{id:e,name:s,location:n,priority:t,markup:r});export const deleteGlobalContent=async e=>t(`${a}/globals`,{ids:e});export const disableGlobalContent=async e=>o(`${a}/globals/disable`,{id:e});export const enableGlobalContent=async e=>o(`${a}/globals/enable`,{id:e});export const setGlobalContentLocked=async({id:e,locked:s})=>o(`${a}/globals/lock`,{id:e,locked:s});export const listExtensions=async({limit:s,offset:o}={})=>e(`${a}/extensions`,{limit:s,offset:o});export const listAvailableExtensions=async()=>e(`${a}/extensions/available`);export const listKnownExtensions=async()=>e(`${a}/extensions/known`);export const getNotifications=async({unreadOnly:s=!1,limit:o,offset:n}={})=>e(`${a}/notifications`,{unreadOnly:s,limit:o,offset:n});export const getUnreadNotificationCount=async()=>e(`${a}/notifications/count`);export const markNotificationRead=async e=>s(`${a}/notifications/${encodeURIComponent(e)}/read`,{});export const markNotificationHandled=async e=>s(`${a}/notifications/${encodeURIComponent(e)}/handled`,{});export const markAllNotificationsRead=async()=>s(`${a}/notifications/read-all`,{});export const dismissNotification=async e=>t(`${a}/notifications/${encodeURIComponent(e)}`);export const listRealtimeConnections=async()=>e(`${a}/realtime/connections`);export const installExtension=async e=>s(`${a}/extensions`,{name:e});export const uninstallExtension=async(e,s=!1)=>t(`${a}/extensions`,{name:e,purgeData:s});export const enableExtension=async e=>s(`${a}/extensions/${encodeURIComponent(e)}/enable`,{});export const disableExtension=async e=>s(`${a}/extensions/${encodeURIComponent(e)}/disable`,{});export const listAdminGlobalContent=async()=>e(`${a}/admin-globals`);export const getAdminGlobalContent=async s=>e(`${a}/admin-globals/${encodeURIComponent(s)}`);export const createAdminGlobalContent=async({name:e,location:o,priority:n,owner:t,markup:r})=>s(`${a}/admin-globals`,{name:e,location:o,priority:n,owner:t,markup:r});export const updateAdminGlobalContent=async(e,{name:s,location:n,priority:t,markup:r})=>o(`${a}/admin-globals/${encodeURIComponent(e)}`,{name:s,location:n,priority:t,markup:r});export const deleteAdminGlobalContent=async e=>t(`${a}/admin-globals`,{ids:e});export const enableAdminGlobalContent=async e=>o(`${a}/admin-globals/${encodeURIComponent(e)}/enable`,{});export const disableAdminGlobalContent=async e=>o(`${a}/admin-globals/${encodeURIComponent(e)}/disable`,{});
2
+ //# sourceMappingURL=C:\Users\dusti\dev\kempo-notifications\dist\kempo\sdk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"src\\kempo\\sdk.js","names":["get","post","put","patch","del","API_BASE","register","async","email","password","name","login","logout","getSession","forgotPassword","resetPassword","token","logoutAll","newPassword","changePassword","currentPassword","verifyEmail","currentUserHasPermission","permission","currentUserHasAllPermissions","permissions","currentUserHasSomePermissions","getAllCurrentUserPermissions","userHasPermission","userid","userHasAllPermissions","userHasSomePermissions","getAllUserPermissions","getUsers","limit","offset","searchUsers","q","inGroup","notInGroup","createUser","emailVerified","deleteUsers","ids","updateUser","id","createdAt","getUser","getUserSessions","deleteUserSession","sessionToken","deleteExpiredUserSessions","getUserGroups","addUserToGroup","groupName","removeUserFromGroup","listGroups","owner","createGroup","description","updateGroup","newName","deleteGroups","names","getGroup","encodeURIComponent","getGroupMembers","addMemberToGroup","userId","removeMemberFromGroup","getGroupPermissions","addPermissionToGroup","permissionName","removePermissionFromGroup","listPermissions","createPermissions","updatePermission","deletePermissions","getSetting","listSettings","setSetting","data","deleteSetting","listPages","showHidden","rescanPages","listTemplates","listDirectories","getPage","file","createPage","directory","template","updatePage","title","author","contents","extraMetadata","deletePages","files","disablePage","enablePage","movePage","newFile","searchPagesByMetadata","query","JSON","stringify","setPageLocked","locked","getTemplate","createTemplate","copyFrom","updateTemplate","markup","deleteTemplates","disableTemplate","enableTemplate","moveTemplate","setTemplateLocked","listFragments","getFragment","createFragment","updateFragment","deleteFragments","moveFragment","disableFragment","enableFragment","setFragmentLocked","listGlobalContent","getGlobalContent","createGlobalContent","location","priority","updateGlobalContent","deleteGlobalContent","disableGlobalContent","enableGlobalContent","setGlobalContentLocked","listExtensions","listAvailableExtensions","listKnownExtensions","listRealtimeConnections","installExtension","uninstallExtension","purgeData","enableExtension","disableExtension","listAdminGlobalContent","getAdminGlobalContent","createAdminGlobalContent","updateAdminGlobalContent","deleteAdminGlobalContent","enableAdminGlobalContent","disableAdminGlobalContent"],"sources":["0"],"sourcesContent":["import { get, post, put, patch, del } from './fetch.js';\nconst API_BASE = '/kempo/api';\n\nexport const register = async ({ email, password, name }) =>\n post(`${API_BASE}/auth/register/email`, { email, password, name });\n\nexport const login = async ({ email, password }) =>\n post(`${API_BASE}/auth/login/email`, { email, password });\n\nexport const logout = async () => post(`${API_BASE}/auth/logout`, {});\n\nexport const getSession = async () => get(`${API_BASE}/auth/session`);\n\nexport const forgotPassword = async ({ email }) =>\n post(`${API_BASE}/forgot-password`, { email });\n\nexport const resetPassword = async ({ token, password, logoutAll = false }) =>\n post(`${API_BASE}/auth/reset-password`, { token, newPassword: password, logoutAll });\n \nexport const changePassword = async ({ currentPassword, newPassword, logoutAll }) => \n post(`${API_BASE}/auth/change-password`, { currentPassword, newPassword, logoutAll });\n\nexport const verifyEmail = async ({ token }) =>\n post(`${API_BASE}/auth/verify-email`, { token });\n\nexport const currentUserHasPermission = async ({ permission }) =>\n get(`${API_BASE}/user/current/permissions`, { permission });\n\nexport const currentUserHasAllPermissions = async ({ permissions }) =>\n get(`${API_BASE}/user/current/permissions/all`, { permissions });\n\nexport const currentUserHasSomePermissions = async ({ permissions }) =>\n get(`${API_BASE}/user/current/permissions/some`, { permissions });\n\nexport const getAllCurrentUserPermissions = async () =>\n get(`${API_BASE}/user/current/permissions/list`);\n\nexport const userHasPermission = async ({ userid, permission }) =>\n get(`${API_BASE}/user/${userid}/permissions`, { permission });\n\nexport const userHasAllPermissions = async ({ userid, permissions }) =>\n get(`${API_BASE}/user/${userid}/permissions/all`, { permissions });\n\nexport const userHasSomePermissions = async ({ userid, permissions }) =>\n get(`${API_BASE}/user/${userid}/permissions/some`, { permissions });\n\nexport const getAllUserPermissions = async ({ userid }) =>\n get(`${API_BASE}/user/${userid}/permissions/list`);\n\nexport const getUsers = async ({ limit, offset } = {}) =>\n get(`${API_BASE}/user`, { limit, offset });\n\nexport const searchUsers = async ({ q, limit, offset, inGroup, notInGroup } = {}) =>\n get(`${API_BASE}/user/search`, { q, limit, offset, inGroup, notInGroup });\n\nexport const createUser = async ({ name, email, password, emailVerified }) =>\n post(`${API_BASE}/user/create`, { name, email, password, emailVerified });\n\nexport const deleteUsers = async (ids) =>\n del(`${API_BASE}/user`, { ids });\n\nexport const updateUser = async ({ id, name, email, emailVerified, createdAt }) =>\n patch(`${API_BASE}/user`, { id, name, email, emailVerified, createdAt });\n\nexport const getUser = async (userid) =>\n get(`${API_BASE}/user/${userid}`);\n\nexport const getUserSessions = async (userid, { limit, offset } = {}) =>\n get(`${API_BASE}/user/${userid}/sessions`, { limit, offset });\n\nexport const deleteUserSession = async (userid, sessionToken) =>\n del(`${API_BASE}/user/${userid}/sessions`, { sessionToken });\n\nexport const deleteExpiredUserSessions = async (userid) =>\n del(`${API_BASE}/user/${userid}/sessions/expired`);\n\nexport const getUserGroups = async (userid, { limit, offset } = {}) =>\n get(`${API_BASE}/user/${userid}/groups`, { limit, offset });\n\nexport const addUserToGroup = async (userid, groupName) =>\n post(`${API_BASE}/user/${userid}/groups`, { groupName });\n\nexport const removeUserFromGroup = async (userid, groupName) =>\n del(`${API_BASE}/user/${userid}/groups`, { groupName });\n\nexport const listGroups = async ({ limit, offset, owner } = {}) =>\n get(`${API_BASE}/groups`, { limit, offset, owner });\n\nexport const createGroup = async ({ name, description }) =>\n post(`${API_BASE}/groups`, { name, description });\n\nexport const updateGroup = async ({ name, newName, description }) =>\n patch(`${API_BASE}/groups`, { name, newName, description });\n\nexport const deleteGroups = async (names) =>\n del(`${API_BASE}/groups`, { names });\n\nexport const getGroup = async (name) =>\n get(`${API_BASE}/groups/${encodeURIComponent(name)}`);\n\nexport const getGroupMembers = async (name, { limit, offset } = {}) =>\n get(`${API_BASE}/groups/${encodeURIComponent(name)}/members`, { limit, offset });\n\nexport const addMemberToGroup = async (name, userId) =>\n post(`${API_BASE}/groups/${encodeURIComponent(name)}/members`, { userId });\n\nexport const removeMemberFromGroup = async (name, userId) =>\n del(`${API_BASE}/groups/${encodeURIComponent(name)}/members`, { userId });\n\nexport const getGroupPermissions = async (name) =>\n get(`${API_BASE}/groups/${encodeURIComponent(name)}/permissions`);\n\nexport const addPermissionToGroup = async (name, permissionName) =>\n post(`${API_BASE}/groups/${encodeURIComponent(name)}/permissions`, { permissionName });\n\nexport const removePermissionFromGroup = async (name, permissionName) =>\n del(`${API_BASE}/groups/${encodeURIComponent(name)}/permissions`, { permissionName });\n\nexport const listPermissions = async ({ limit, offset, owner } = {}) =>\n get(`${API_BASE}/permissions`, { limit, offset, owner });\n\nexport const createPermissions = async (permissions) =>\n post(`${API_BASE}/permissions`, { permissions });\n\nexport const updatePermission = async ({ name, description }) =>\n patch(`${API_BASE}/permissions`, { name, description });\n\nexport const deletePermissions = async (names) =>\n del(`${API_BASE}/permissions`, { names });\n\nexport const getSetting = async (owner, name) =>\n get(`${API_BASE}/settings/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`);\n\nexport const listSettings = async ({ owner, limit, offset } = {}) =>\n get(`${API_BASE}/settings/${encodeURIComponent(owner)}`, { limit, offset });\n\nexport const setSetting = async (owner, name, data) =>\n post(`${API_BASE}/settings/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`, data);\n\nexport const deleteSetting = async (owner, name) =>\n del(`${API_BASE}/settings/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`);\n\n/*\n Pages\n*/\n\nexport const listPages = async ({ owner, showHidden, offset, limit } = {}) =>\n get(`${API_BASE}/pages`, { owner, showHidden, offset, limit });\n\nexport const rescanPages = async () =>\n post(`${API_BASE}/pages/rescan`, {});\n\nexport const listTemplates = async () =>\n get(`${API_BASE}/templates`);\n\nexport const listDirectories = async () =>\n get(`${API_BASE}/pages/directories`);\n\nexport const getPage = async (file) =>\n get(`${API_BASE}/pages/file`, { file });\n\nexport const createPage = async ({ directory, name, template }) =>\n post(`${API_BASE}/pages`, { directory, name, template });\n\nexport const updatePage = async ({ file, name, title, description, author, template, contents, extraMetadata }) =>\n put(`${API_BASE}/pages/file`, { file, name, title, description, author, template, contents, extraMetadata });\n\nexport const deletePages = async (files) =>\n del(`${API_BASE}/pages`, { files });\n\nexport const disablePage = async (file) =>\n put(`${API_BASE}/pages/disable`, { file });\n\nexport const enablePage = async (file) =>\n put(`${API_BASE}/pages/enable`, { file });\n\nexport const movePage = async ({ file, newFile }) =>\n patch(`${API_BASE}/pages/file`, { file, newFile });\n\nexport const searchPagesByMetadata = async (query) =>\n get(`${API_BASE}/pages/search`, { query: JSON.stringify(query) });\n\nexport const setPageLocked = async ({ file, locked }) =>\n put(`${API_BASE}/pages/lock`, { file, locked });\n\n/*\n Templates\n*/\n\nexport const getTemplate = async (file) =>\n get(`${API_BASE}/templates/file`, { file });\n\nexport const createTemplate = async ({ directory, name, copyFrom }) =>\n post(`${API_BASE}/templates`, { directory, name, copyFrom });\n\nexport const updateTemplate = async ({ file, name, author, markup }) =>\n put(`${API_BASE}/templates/file`, { file, name, author, markup });\n\nexport const deleteTemplates = async (files) =>\n del(`${API_BASE}/templates`, { files });\n\nexport const disableTemplate = async (file) =>\n put(`${API_BASE}/templates/disable`, { file });\n\nexport const enableTemplate = async (file) =>\n put(`${API_BASE}/templates/enable`, { file });\n\nexport const moveTemplate = async ({ file, newFile }) =>\n patch(`${API_BASE}/templates/file`, { file, newFile });\n\nexport const setTemplateLocked = async ({ file, locked }) =>\n put(`${API_BASE}/templates/lock`, { file, locked });\n\n/*\n Fragments\n*/\n\nexport const listFragments = async () =>\n get(`${API_BASE}/fragments`);\n\nexport const getFragment = async (file) =>\n get(`${API_BASE}/fragments/file`, { file });\n\nexport const createFragment = async ({ directory, name }) =>\n post(`${API_BASE}/fragments`, { directory, name });\n\nexport const updateFragment = async ({ file, author, markup }) =>\n put(`${API_BASE}/fragments/file`, { file, author, markup });\n\nexport const deleteFragments = async (files) =>\n del(`${API_BASE}/fragments`, { files });\n\nexport const moveFragment = async ({ file, newFile }) =>\n patch(`${API_BASE}/fragments/file`, { file, newFile });\n\nexport const disableFragment = async (file) =>\n put(`${API_BASE}/fragments/disable`, { file });\n\nexport const enableFragment = async (file) =>\n put(`${API_BASE}/fragments/enable`, { file });\n\nexport const setFragmentLocked = async ({ file, locked }) =>\n put(`${API_BASE}/fragments/lock`, { file, locked });\n\n/*\n Global Content\n*/\n\nexport const listGlobalContent = async () =>\n get(`${API_BASE}/globals`);\n\nexport const getGlobalContent = async (id) =>\n get(`${API_BASE}/globals/entry`, { id });\n\nexport const createGlobalContent = async ({ name, location, priority }) =>\n post(`${API_BASE}/globals`, { name, location, priority });\n\nexport const updateGlobalContent = async ({ id, name, location, priority, markup }) =>\n put(`${API_BASE}/globals/entry`, { id, name, location, priority, markup });\n\nexport const deleteGlobalContent = async (ids) =>\n del(`${API_BASE}/globals`, { ids });\n\nexport const disableGlobalContent = async (id) =>\n put(`${API_BASE}/globals/disable`, { id });\n\nexport const enableGlobalContent = async (id) =>\n put(`${API_BASE}/globals/enable`, { id });\n\nexport const setGlobalContentLocked = async ({ id, locked }) =>\n put(`${API_BASE}/globals/lock`, { id, locked });\n\n/*\n Extensions\n*/\n\nexport const listExtensions = async ({ limit, offset } = {}) =>\n get(`${API_BASE}/extensions`, { limit, offset });\n\nexport const listAvailableExtensions = async () =>\n get(`${API_BASE}/extensions/available`);\n\nexport const listKnownExtensions = async () =>\n get(`${API_BASE}/extensions/known`);\n\nexport const listRealtimeConnections = async () =>\n get(`${API_BASE}/realtime/connections`);\n\nexport const installExtension = async (name) =>\n post(`${API_BASE}/extensions`, { name });\n\nexport const uninstallExtension = async (name, purgeData = false) =>\n del(`${API_BASE}/extensions`, { name, purgeData });\n\nexport const enableExtension = async (name) =>\n post(`${API_BASE}/extensions/${encodeURIComponent(name)}/enable`, {});\n\nexport const disableExtension = async (name) =>\n post(`${API_BASE}/extensions/${encodeURIComponent(name)}/disable`, {});\n\n/*\n Admin Global Content\n*/\n\nexport const listAdminGlobalContent = async () =>\n get(`${API_BASE}/admin-globals`);\n\nexport const getAdminGlobalContent = async (id) =>\n get(`${API_BASE}/admin-globals/${encodeURIComponent(id)}`);\n\nexport const createAdminGlobalContent = async ({ name, location, priority, owner, markup }) =>\n post(`${API_BASE}/admin-globals`, { name, location, priority, owner, markup });\n\nexport const updateAdminGlobalContent = async (id, { name, location, priority, markup }) =>\n put(`${API_BASE}/admin-globals/${encodeURIComponent(id)}`, { name, location, priority, markup });\n\nexport const deleteAdminGlobalContent = async (ids) =>\n del(`${API_BASE}/admin-globals`, { ids });\n\nexport const enableAdminGlobalContent = async (id) =>\n put(`${API_BASE}/admin-globals/${encodeURIComponent(id)}/enable`, {});\n\nexport const disableAdminGlobalContent = async (id) =>\n put(`${API_BASE}/admin-globals/${encodeURIComponent(id)}/disable`, {});\n\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"src\\kempo\\sdk.js","names":["get","post","put","patch","del","API_BASE","register","async","email","password","name","login","logout","getSession","forgotPassword","resetPassword","token","logoutAll","newPassword","changePassword","currentPassword","verifyEmail","currentUserHasPermission","permission","currentUserHasAllPermissions","permissions","currentUserHasSomePermissions","getAllCurrentUserPermissions","userHasPermission","userid","userHasAllPermissions","userHasSomePermissions","getAllUserPermissions","getUsers","limit","offset","searchUsers","q","inGroup","notInGroup","createUser","emailVerified","deleteUsers","ids","updateUser","id","createdAt","getUser","getUserSessions","deleteUserSession","sessionToken","deleteExpiredUserSessions","getUserGroups","addUserToGroup","groupName","removeUserFromGroup","listGroups","owner","createGroup","description","updateGroup","newName","deleteGroups","names","getGroup","encodeURIComponent","getGroupMembers","addMemberToGroup","userId","removeMemberFromGroup","getGroupPermissions","addPermissionToGroup","permissionName","removePermissionFromGroup","listPermissions","createPermissions","updatePermission","deletePermissions","getSetting","listSettings","setSetting","data","deleteSetting","listPages","showHidden","rescanPages","listTemplates","listDirectories","getPage","file","createPage","directory","template","updatePage","title","author","contents","extraMetadata","deletePages","files","disablePage","enablePage","movePage","newFile","searchPagesByMetadata","query","JSON","stringify","setPageLocked","locked","getTemplate","createTemplate","copyFrom","updateTemplate","markup","deleteTemplates","disableTemplate","enableTemplate","moveTemplate","setTemplateLocked","listFragments","getFragment","createFragment","updateFragment","deleteFragments","moveFragment","disableFragment","enableFragment","setFragmentLocked","listGlobalContent","getGlobalContent","createGlobalContent","location","priority","updateGlobalContent","deleteGlobalContent","disableGlobalContent","enableGlobalContent","setGlobalContentLocked","listExtensions","listAvailableExtensions","listKnownExtensions","getNotifications","unreadOnly","getUnreadNotificationCount","markNotificationRead","markNotificationHandled","markAllNotificationsRead","dismissNotification","listRealtimeConnections","installExtension","uninstallExtension","purgeData","enableExtension","disableExtension","listAdminGlobalContent","getAdminGlobalContent","createAdminGlobalContent","updateAdminGlobalContent","deleteAdminGlobalContent","enableAdminGlobalContent","disableAdminGlobalContent"],"sources":["0"],"sourcesContent":["import { get, post, put, patch, del } from './fetch.js';\nconst API_BASE = '/kempo/api';\n\nexport const register = async ({ email, password, name }) =>\n post(`${API_BASE}/auth/register/email`, { email, password, name });\n\nexport const login = async ({ email, password }) =>\n post(`${API_BASE}/auth/login/email`, { email, password });\n\nexport const logout = async () => post(`${API_BASE}/auth/logout`, {});\n\nexport const getSession = async () => get(`${API_BASE}/auth/session`);\n\nexport const forgotPassword = async ({ email }) =>\n post(`${API_BASE}/forgot-password`, { email });\n\nexport const resetPassword = async ({ token, password, logoutAll = false }) =>\n post(`${API_BASE}/auth/reset-password`, { token, newPassword: password, logoutAll });\n \nexport const changePassword = async ({ currentPassword, newPassword, logoutAll }) => \n post(`${API_BASE}/auth/change-password`, { currentPassword, newPassword, logoutAll });\n\nexport const verifyEmail = async ({ token }) =>\n post(`${API_BASE}/auth/verify-email`, { token });\n\nexport const currentUserHasPermission = async ({ permission }) =>\n get(`${API_BASE}/user/current/permissions`, { permission });\n\nexport const currentUserHasAllPermissions = async ({ permissions }) =>\n get(`${API_BASE}/user/current/permissions/all`, { permissions });\n\nexport const currentUserHasSomePermissions = async ({ permissions }) =>\n get(`${API_BASE}/user/current/permissions/some`, { permissions });\n\nexport const getAllCurrentUserPermissions = async () =>\n get(`${API_BASE}/user/current/permissions/list`);\n\nexport const userHasPermission = async ({ userid, permission }) =>\n get(`${API_BASE}/user/${userid}/permissions`, { permission });\n\nexport const userHasAllPermissions = async ({ userid, permissions }) =>\n get(`${API_BASE}/user/${userid}/permissions/all`, { permissions });\n\nexport const userHasSomePermissions = async ({ userid, permissions }) =>\n get(`${API_BASE}/user/${userid}/permissions/some`, { permissions });\n\nexport const getAllUserPermissions = async ({ userid }) =>\n get(`${API_BASE}/user/${userid}/permissions/list`);\n\nexport const getUsers = async ({ limit, offset } = {}) =>\n get(`${API_BASE}/user`, { limit, offset });\n\nexport const searchUsers = async ({ q, limit, offset, inGroup, notInGroup } = {}) =>\n get(`${API_BASE}/user/search`, { q, limit, offset, inGroup, notInGroup });\n\nexport const createUser = async ({ name, email, password, emailVerified }) =>\n post(`${API_BASE}/user/create`, { name, email, password, emailVerified });\n\nexport const deleteUsers = async (ids) =>\n del(`${API_BASE}/user`, { ids });\n\nexport const updateUser = async ({ id, name, email, emailVerified, createdAt }) =>\n patch(`${API_BASE}/user`, { id, name, email, emailVerified, createdAt });\n\nexport const getUser = async (userid) =>\n get(`${API_BASE}/user/${userid}`);\n\nexport const getUserSessions = async (userid, { limit, offset } = {}) =>\n get(`${API_BASE}/user/${userid}/sessions`, { limit, offset });\n\nexport const deleteUserSession = async (userid, sessionToken) =>\n del(`${API_BASE}/user/${userid}/sessions`, { sessionToken });\n\nexport const deleteExpiredUserSessions = async (userid) =>\n del(`${API_BASE}/user/${userid}/sessions/expired`);\n\nexport const getUserGroups = async (userid, { limit, offset } = {}) =>\n get(`${API_BASE}/user/${userid}/groups`, { limit, offset });\n\nexport const addUserToGroup = async (userid, groupName) =>\n post(`${API_BASE}/user/${userid}/groups`, { groupName });\n\nexport const removeUserFromGroup = async (userid, groupName) =>\n del(`${API_BASE}/user/${userid}/groups`, { groupName });\n\nexport const listGroups = async ({ limit, offset, owner } = {}) =>\n get(`${API_BASE}/groups`, { limit, offset, owner });\n\nexport const createGroup = async ({ name, description }) =>\n post(`${API_BASE}/groups`, { name, description });\n\nexport const updateGroup = async ({ name, newName, description }) =>\n patch(`${API_BASE}/groups`, { name, newName, description });\n\nexport const deleteGroups = async (names) =>\n del(`${API_BASE}/groups`, { names });\n\nexport const getGroup = async (name) =>\n get(`${API_BASE}/groups/${encodeURIComponent(name)}`);\n\nexport const getGroupMembers = async (name, { limit, offset } = {}) =>\n get(`${API_BASE}/groups/${encodeURIComponent(name)}/members`, { limit, offset });\n\nexport const addMemberToGroup = async (name, userId) =>\n post(`${API_BASE}/groups/${encodeURIComponent(name)}/members`, { userId });\n\nexport const removeMemberFromGroup = async (name, userId) =>\n del(`${API_BASE}/groups/${encodeURIComponent(name)}/members`, { userId });\n\nexport const getGroupPermissions = async (name) =>\n get(`${API_BASE}/groups/${encodeURIComponent(name)}/permissions`);\n\nexport const addPermissionToGroup = async (name, permissionName) =>\n post(`${API_BASE}/groups/${encodeURIComponent(name)}/permissions`, { permissionName });\n\nexport const removePermissionFromGroup = async (name, permissionName) =>\n del(`${API_BASE}/groups/${encodeURIComponent(name)}/permissions`, { permissionName });\n\nexport const listPermissions = async ({ limit, offset, owner } = {}) =>\n get(`${API_BASE}/permissions`, { limit, offset, owner });\n\nexport const createPermissions = async (permissions) =>\n post(`${API_BASE}/permissions`, { permissions });\n\nexport const updatePermission = async ({ name, description }) =>\n patch(`${API_BASE}/permissions`, { name, description });\n\nexport const deletePermissions = async (names) =>\n del(`${API_BASE}/permissions`, { names });\n\nexport const getSetting = async (owner, name) =>\n get(`${API_BASE}/settings/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`);\n\nexport const listSettings = async ({ owner, limit, offset } = {}) =>\n get(`${API_BASE}/settings/${encodeURIComponent(owner)}`, { limit, offset });\n\nexport const setSetting = async (owner, name, data) =>\n post(`${API_BASE}/settings/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`, data);\n\nexport const deleteSetting = async (owner, name) =>\n del(`${API_BASE}/settings/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`);\n\n/*\n Pages\n*/\n\nexport const listPages = async ({ owner, showHidden, offset, limit } = {}) =>\n get(`${API_BASE}/pages`, { owner, showHidden, offset, limit });\n\nexport const rescanPages = async () =>\n post(`${API_BASE}/pages/rescan`, {});\n\nexport const listTemplates = async () =>\n get(`${API_BASE}/templates`);\n\nexport const listDirectories = async () =>\n get(`${API_BASE}/pages/directories`);\n\nexport const getPage = async (file) =>\n get(`${API_BASE}/pages/file`, { file });\n\nexport const createPage = async ({ directory, name, template }) =>\n post(`${API_BASE}/pages`, { directory, name, template });\n\nexport const updatePage = async ({ file, name, title, description, author, template, contents, extraMetadata }) =>\n put(`${API_BASE}/pages/file`, { file, name, title, description, author, template, contents, extraMetadata });\n\nexport const deletePages = async (files) =>\n del(`${API_BASE}/pages`, { files });\n\nexport const disablePage = async (file) =>\n put(`${API_BASE}/pages/disable`, { file });\n\nexport const enablePage = async (file) =>\n put(`${API_BASE}/pages/enable`, { file });\n\nexport const movePage = async ({ file, newFile }) =>\n patch(`${API_BASE}/pages/file`, { file, newFile });\n\nexport const searchPagesByMetadata = async (query) =>\n get(`${API_BASE}/pages/search`, { query: JSON.stringify(query) });\n\nexport const setPageLocked = async ({ file, locked }) =>\n put(`${API_BASE}/pages/lock`, { file, locked });\n\n/*\n Templates\n*/\n\nexport const getTemplate = async (file) =>\n get(`${API_BASE}/templates/file`, { file });\n\nexport const createTemplate = async ({ directory, name, copyFrom }) =>\n post(`${API_BASE}/templates`, { directory, name, copyFrom });\n\nexport const updateTemplate = async ({ file, name, author, markup }) =>\n put(`${API_BASE}/templates/file`, { file, name, author, markup });\n\nexport const deleteTemplates = async (files) =>\n del(`${API_BASE}/templates`, { files });\n\nexport const disableTemplate = async (file) =>\n put(`${API_BASE}/templates/disable`, { file });\n\nexport const enableTemplate = async (file) =>\n put(`${API_BASE}/templates/enable`, { file });\n\nexport const moveTemplate = async ({ file, newFile }) =>\n patch(`${API_BASE}/templates/file`, { file, newFile });\n\nexport const setTemplateLocked = async ({ file, locked }) =>\n put(`${API_BASE}/templates/lock`, { file, locked });\n\n/*\n Fragments\n*/\n\nexport const listFragments = async () =>\n get(`${API_BASE}/fragments`);\n\nexport const getFragment = async (file) =>\n get(`${API_BASE}/fragments/file`, { file });\n\nexport const createFragment = async ({ directory, name }) =>\n post(`${API_BASE}/fragments`, { directory, name });\n\nexport const updateFragment = async ({ file, author, markup }) =>\n put(`${API_BASE}/fragments/file`, { file, author, markup });\n\nexport const deleteFragments = async (files) =>\n del(`${API_BASE}/fragments`, { files });\n\nexport const moveFragment = async ({ file, newFile }) =>\n patch(`${API_BASE}/fragments/file`, { file, newFile });\n\nexport const disableFragment = async (file) =>\n put(`${API_BASE}/fragments/disable`, { file });\n\nexport const enableFragment = async (file) =>\n put(`${API_BASE}/fragments/enable`, { file });\n\nexport const setFragmentLocked = async ({ file, locked }) =>\n put(`${API_BASE}/fragments/lock`, { file, locked });\n\n/*\n Global Content\n*/\n\nexport const listGlobalContent = async () =>\n get(`${API_BASE}/globals`);\n\nexport const getGlobalContent = async (id) =>\n get(`${API_BASE}/globals/entry`, { id });\n\nexport const createGlobalContent = async ({ name, location, priority }) =>\n post(`${API_BASE}/globals`, { name, location, priority });\n\nexport const updateGlobalContent = async ({ id, name, location, priority, markup }) =>\n put(`${API_BASE}/globals/entry`, { id, name, location, priority, markup });\n\nexport const deleteGlobalContent = async (ids) =>\n del(`${API_BASE}/globals`, { ids });\n\nexport const disableGlobalContent = async (id) =>\n put(`${API_BASE}/globals/disable`, { id });\n\nexport const enableGlobalContent = async (id) =>\n put(`${API_BASE}/globals/enable`, { id });\n\nexport const setGlobalContentLocked = async ({ id, locked }) =>\n put(`${API_BASE}/globals/lock`, { id, locked });\n\n/*\n Extensions\n*/\n\nexport const listExtensions = async ({ limit, offset } = {}) =>\n get(`${API_BASE}/extensions`, { limit, offset });\n\nexport const listAvailableExtensions = async () =>\n get(`${API_BASE}/extensions/available`);\n\nexport const listKnownExtensions = async () =>\n get(`${API_BASE}/extensions/known`);\n\nexport const getNotifications = async ({ unreadOnly = false, limit, offset } = {}) =>\n get(`${API_BASE}/notifications`, { unreadOnly, limit, offset });\n\nexport const getUnreadNotificationCount = async () =>\n get(`${API_BASE}/notifications/count`);\n\nexport const markNotificationRead = async (id) =>\n post(`${API_BASE}/notifications/${encodeURIComponent(id)}/read`, {});\n\nexport const markNotificationHandled = async (id) =>\n post(`${API_BASE}/notifications/${encodeURIComponent(id)}/handled`, {});\n\nexport const markAllNotificationsRead = async () =>\n post(`${API_BASE}/notifications/read-all`, {});\n\nexport const dismissNotification = async (id) =>\n del(`${API_BASE}/notifications/${encodeURIComponent(id)}`);\n\nexport const listRealtimeConnections = async () =>\n get(`${API_BASE}/realtime/connections`);\n\nexport const installExtension = async (name) =>\n post(`${API_BASE}/extensions`, { name });\n\nexport const uninstallExtension = async (name, purgeData = false) =>\n del(`${API_BASE}/extensions`, { name, purgeData });\n\nexport const enableExtension = async (name) =>\n post(`${API_BASE}/extensions/${encodeURIComponent(name)}/enable`, {});\n\nexport const disableExtension = async (name) =>\n post(`${API_BASE}/extensions/${encodeURIComponent(name)}/disable`, {});\n\n/*\n Admin Global Content\n*/\n\nexport const listAdminGlobalContent = async () =>\n get(`${API_BASE}/admin-globals`);\n\nexport const getAdminGlobalContent = async (id) =>\n get(`${API_BASE}/admin-globals/${encodeURIComponent(id)}`);\n\nexport const createAdminGlobalContent = async ({ name, location, priority, owner, markup }) =>\n post(`${API_BASE}/admin-globals`, { name, location, priority, owner, markup });\n\nexport const updateAdminGlobalContent = async (id, { name, location, priority, markup }) =>\n put(`${API_BASE}/admin-globals/${encodeURIComponent(id)}`, { name, location, priority, markup });\n\nexport const deleteAdminGlobalContent = async (ids) =>\n del(`${API_BASE}/admin-globals`, { ids });\n\nexport const enableAdminGlobalContent = async (id) =>\n put(`${API_BASE}/admin-globals/${encodeURIComponent(id)}/enable`, {});\n\nexport const disableAdminGlobalContent = async (id) =>\n put(`${API_BASE}/admin-globals/${encodeURIComponent(id)}/disable`, {});\n\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo",
3
- "version": "4.4.3",
3
+ "version": "4.4.4",
4
4
  "type": "module",
5
5
  "description": "A fullstack CMS framework for creating websites with user authentication",
6
6
  "scripts": {
@@ -59,7 +59,7 @@
59
59
  "peerDependencies": {
60
60
  "kempo-css": ">=2.2.5",
61
61
  "kempo-server": ">=3.4.0",
62
- "kempo-ui": ">=0.5.0"
62
+ "kempo-ui": ">=0.5.1"
63
63
  },
64
64
  "devDependencies": {
65
65
  "esbuild": "^0.28.2",
@@ -161,6 +161,7 @@ const DEFAULT_SETTINGS = [
161
161
  { owner: 'system', name: 'allow_registration', value: true, type: 'boolean', isPublic: true, description: 'Allow new users to register' },
162
162
  { owner: 'system', name: 'require_email_verification', value: false, type: 'boolean', isPublic: false, description: 'Require email verification before account access' },
163
163
  { owner: 'system', name: 'verification_url', value: 'http://localhost:3000/verify-email/{{token}}', type: 'string', isPublic: false, description: 'Email verification URL template' },
164
+ { owner: 'system', name: 'notification_retention_days', value: 90, type: 'number', isPublic: false, description: 'Days to keep notifications before they are deleted (0 keeps them forever)' },
164
165
  { owner: 'system', name: 'password_reset_url', value: 'http://localhost:3000/reset-password/{{token}}', type: 'string', isPublic: false, description: 'Password reset URL template' },
165
166
  ];
166
167
 
@@ -1,4 +1,5 @@
1
- import { pgTable, text, timestamp, boolean, index, jsonb, bigserial, bigint } from 'drizzle-orm/pg-core';
1
+ import { sql } from 'drizzle-orm';
2
+ import { pgTable, text, timestamp, boolean, index, uniqueIndex, primaryKey, jsonb, bigserial, bigint } from 'drizzle-orm/pg-core';
2
3
 
3
4
  /*
4
5
  Auth Tables
@@ -143,6 +144,50 @@ export const realtimeChannel = pgTable('realtimeChannel', {
143
144
  prunedThrough: bigint('prunedThrough', { mode: 'number' }).notNull().default(0),
144
145
  });
145
146
 
147
+ /*
148
+ Notification System
149
+ */
150
+
151
+ /*
152
+ What happened, written once however many people it was sent to. `owner` is the extension name, or
153
+ `kempo` for core. A `dedupeKey` makes (owner, dedupeKey) one row that is refreshed instead of repeated.
154
+ */
155
+ export const notification = pgTable('notification', {
156
+ id: text('id').primaryKey(),
157
+ owner: text('owner').notNull(),
158
+ title: text('title').notNull(),
159
+ message: text('message'),
160
+ level: text('level').notNull().default('info'),
161
+ actions: jsonb('actions').notNull().default([]),
162
+ dedupeKey: text('dedupeKey'),
163
+ createdAt: timestamp('createdAt').notNull(),
164
+ updatedAt: timestamp('updatedAt').notNull(),
165
+ expiresAt: timestamp('expiresAt'),
166
+ }, table => [
167
+ uniqueIndex('notification_owner_dedupeKey_idx').on(table.owner, table.dedupeKey).where(sql`${table.dedupeKey} is not null`),
168
+ index('notification_updatedAt_idx').on(table.updatedAt),
169
+ ]);
170
+
171
+ /*
172
+ One row per person it was sent to, written when the notification is created so each person's read,
173
+ handled and dismissed state is their own and their history does not change if they later lose the
174
+ permission that earned them the notification.
175
+ */
176
+ export const notificationRecipient = pgTable('notificationRecipient', {
177
+ notificationId: text('notificationId')
178
+ .notNull()
179
+ .references(() => notification.id, { onDelete: 'cascade' }),
180
+ userId: text('userId')
181
+ .notNull()
182
+ .references(() => user.id, { onDelete: 'cascade' }),
183
+ readAt: timestamp('readAt'),
184
+ handledAt: timestamp('handledAt'),
185
+ dismissedAt: timestamp('dismissedAt'),
186
+ }, table => [
187
+ primaryKey({ columns: [table.notificationId, table.userId] }),
188
+ index('notificationRecipient_userId_idx').on(table.userId),
189
+ ]);
190
+
146
191
  /*
147
192
  Triggers to prevent deletion of system resources
148
193
  */
package/server/sdk.js CHANGED
@@ -196,6 +196,19 @@ export { default as listExtensions } from './utils/extensions/listExtensions.js'
196
196
  export { default as uninstallExtension } from './utils/extensions/uninstallExtension.js';
197
197
  export { default as updateExtension } from './utils/extensions/updateExtension.js';
198
198
 
199
+ /*
200
+ Notifications
201
+ */
202
+
203
+ export { default as createNotification } from './utils/notifications/createNotification.js';
204
+ export { default as deleteNotification } from './utils/notifications/deleteNotification.js';
205
+ export { default as getNotifications } from './utils/notifications/getNotifications.js';
206
+ export { default as getUnreadCount } from './utils/notifications/getUnreadCount.js';
207
+ export { default as markAllRead } from './utils/notifications/markAllRead.js';
208
+ export { default as markHandled } from './utils/notifications/markHandled.js';
209
+ export { default as markRead } from './utils/notifications/markRead.js';
210
+ export { default as pruneNotifications } from './utils/notifications/pruneNotifications.js';
211
+
199
212
  /*
200
213
  Realtime
201
214
  */
@@ -0,0 +1,143 @@
1
+ import crypto from 'crypto';
2
+ import { eq, inArray, or, sql, getTableColumns } from 'drizzle-orm';
3
+ import db from '../../db/index.js';
4
+ import { notification, notificationRecipient, user, userGroup, groupPermission } from '../../db/schema.js';
5
+ import triggerHook from '../hooks/triggerHook.js';
6
+ import pruneNotifications from './pruneNotifications.js';
7
+ import { LEVELS, MAX_TITLE_LENGTH, MAX_MESSAGE_LENGTH, validateActions } from './helpers.js';
8
+
9
+ const PRUNE_INTERVAL_MS = 3600000;
10
+ const INSERT_CHUNK = 5000;
11
+ let lastPrune = 0;
12
+
13
+ const isText = (value, max) => typeof value === 'string' && value.trim().length > 0 && value.length <= max;
14
+
15
+ /*
16
+ Sends a notification to the union of the people named by `userIds`, everyone who holds `permission`
17
+ (through any group, and every Administrator), and every member of `group`.
18
+
19
+ The recipients are fixed here, when it is created, and one row is written for each. That is what keeps
20
+ a person's read state their own and keeps their history intact if they later lose the permission, or
21
+ gain it after the notification was sent. The cost is a row per recipient for a broadcast.
22
+
23
+ A `dedupeKey` makes (owner, dedupeKey) a single notification. Raising it again updates that notification
24
+ in place and re-opens it for every recipient: unread, no longer handled, no longer dismissed. A person
25
+ who read it and an unread one both end up with it unread and at the top of their list; people who
26
+ received the earlier one but are not recipients this time are left as they were.
27
+
28
+ Targets that resolve to nobody are not an error: nothing is stored and `notification` is null.
29
+ */
30
+ export default async ({ owner, title, message = null, level = 'info', actions, dedupeKey = null, expiresAt = null, userIds, permission, group } = {}) => {
31
+ if(!isText(owner, 100)){
32
+ return [{ code: 400, msg: 'Owner is required' }, null];
33
+ }
34
+
35
+ if(!isText(title, MAX_TITLE_LENGTH)){
36
+ return [{ code: 400, msg: `Title is required and must be at most ${MAX_TITLE_LENGTH} characters` }, null];
37
+ }
38
+
39
+ if(message !== null && !(typeof message === 'string' && message.length <= MAX_MESSAGE_LENGTH)){
40
+ return [{ code: 400, msg: `Message must be a string of at most ${MAX_MESSAGE_LENGTH} characters` }, null];
41
+ }
42
+
43
+ if(!LEVELS.includes(level)){
44
+ return [{ code: 400, msg: `Level must be one of ${LEVELS.join(', ')}` }, null];
45
+ }
46
+
47
+ if(dedupeKey !== null && !isText(dedupeKey, 200)){
48
+ return [{ code: 400, msg: 'dedupeKey must be a non-empty string of at most 200 characters' }, null];
49
+ }
50
+
51
+ const expires = expiresAt === null || expiresAt === undefined ? null : new Date(expiresAt);
52
+
53
+ if(expires && (Number.isNaN(expires.getTime()) || expires.getTime() <= Date.now())){
54
+ return [{ code: 400, msg: 'expiresAt must be a date in the future' }, null];
55
+ }
56
+
57
+ const [actionsError, cleanActions] = validateActions(actions);
58
+
59
+ if(actionsError){
60
+ return [actionsError, null];
61
+ }
62
+
63
+ if(userIds === undefined && permission === undefined && group === undefined){
64
+ return [{ code: 400, msg: 'At least one of userIds, permission or group is required' }, null];
65
+ }
66
+
67
+ if(userIds !== undefined && (!Array.isArray(userIds) || userIds.some(id => typeof id !== 'string' || !id))){
68
+ return [{ code: 400, msg: 'userIds must be an array of user ids' }, null];
69
+ }
70
+
71
+ if((permission !== undefined && !isText(permission, 200)) || (group !== undefined && !isText(group, 200))){
72
+ return [{ code: 400, msg: 'permission and group must be names' }, null];
73
+ }
74
+
75
+ try {
76
+ const recipients = new Set();
77
+
78
+ if(userIds?.length){
79
+ const found = await db.select({ id: user.id }).from(user).where(inArray(user.id, [...new Set(userIds)]));
80
+ found.forEach(row => recipients.add(row.id));
81
+ }
82
+
83
+ if(permission){
84
+ const holders = await db
85
+ .selectDistinct({ id: userGroup.userId })
86
+ .from(userGroup)
87
+ .where(or(
88
+ eq(userGroup.groupName, 'system:Administrators'),
89
+ inArray(userGroup.groupName, db.select({ name: groupPermission.groupName }).from(groupPermission).where(eq(groupPermission.permissionName, permission)))
90
+ ));
91
+ holders.forEach(row => recipients.add(row.id));
92
+ }
93
+
94
+ if(group){
95
+ const members = await db.selectDistinct({ id: userGroup.userId }).from(userGroup).where(eq(userGroup.groupName, group));
96
+ members.forEach(row => recipients.add(row.id));
97
+ }
98
+
99
+ if(!recipients.size){
100
+ return [null, { notification: null, recipientIds: [], refreshed: false }];
101
+ }
102
+
103
+ const now = new Date();
104
+ const recipientIds = [...recipients];
105
+
106
+ const created = await db.transaction(async tx => {
107
+ const [row] = await tx
108
+ .insert(notification)
109
+ .values({ id: crypto.randomUUID(), owner, title: title.trim(), message, level, actions: cleanActions, dedupeKey, createdAt: now, updatedAt: now, expiresAt: expires })
110
+ .onConflictDoUpdate({
111
+ target: [notification.owner, notification.dedupeKey],
112
+ targetWhere: sql`${notification.dedupeKey} is not null`,
113
+ set: { title: title.trim(), message, level, actions: cleanActions, updatedAt: now, expiresAt: expires }
114
+ })
115
+ .returning({ ...getTableColumns(notification), inserted: sql`(xmax = 0)`.mapWith(Boolean) });
116
+
117
+ for(let i = 0; i < recipientIds.length; i += INSERT_CHUNK){
118
+ await tx
119
+ .insert(notificationRecipient)
120
+ .values(recipientIds.slice(i, i + INSERT_CHUNK).map(userId => ({ notificationId: row.id, userId })))
121
+ .onConflictDoUpdate({
122
+ target: [notificationRecipient.notificationId, notificationRecipient.userId],
123
+ set: { readAt: null, handledAt: null, dismissedAt: null }
124
+ });
125
+ }
126
+
127
+ return row;
128
+ });
129
+
130
+ const { inserted, ...saved } = created;
131
+
132
+ await triggerHook('notification:created', { notification: saved, recipientIds, refreshed: !inserted });
133
+
134
+ if(Date.now() - lastPrune > PRUNE_INTERVAL_MS){
135
+ lastPrune = Date.now();
136
+ pruneNotifications().catch(() => {});
137
+ }
138
+
139
+ return [null, { notification: saved, recipientIds, refreshed: !inserted }];
140
+ } catch(error) {
141
+ return [{ code: 500, msg: 'Failed to create notification' }, null];
142
+ }
143
+ };
@@ -0,0 +1,36 @@
1
+ import { and, eq } from 'drizzle-orm';
2
+ import db from '../../db/index.js';
3
+ import { notification, notificationRecipient } from '../../db/schema.js';
4
+
5
+ /*
6
+ Removes a notification for one person (`userId`), which hides it from their list and count but leaves
7
+ everyone else's copy and the stored row alone, or for everyone (`everyone: true`), which deletes it.
8
+ The HTTP routes only ever dismiss for the signed-in user; deleting for everyone is for server code.
9
+ */
10
+ export default async ({ notificationId, userId, everyone = false } = {}) => {
11
+ if(!notificationId){
12
+ return [{ code: 400, msg: 'Notification ID is required' }, null];
13
+ }
14
+
15
+ if(!everyone && !userId){
16
+ return [{ code: 400, msg: 'User ID is required unless deleting for everyone' }, null];
17
+ }
18
+
19
+ try {
20
+ const removed = everyone
21
+ ? await db.delete(notification).where(eq(notification.id, notificationId)).returning({ id: notification.id })
22
+ : await db
23
+ .update(notificationRecipient)
24
+ .set({ dismissedAt: new Date() })
25
+ .where(and(eq(notificationRecipient.userId, userId), eq(notificationRecipient.notificationId, notificationId)))
26
+ .returning({ id: notificationRecipient.notificationId });
27
+
28
+ if(!removed.length){
29
+ return [{ code: 404, msg: 'Notification not found' }, null];
30
+ }
31
+
32
+ return [null, { id: notificationId }];
33
+ } catch(error) {
34
+ return [{ code: 500, msg: 'Failed to delete notification' }, null];
35
+ }
36
+ };
@@ -0,0 +1,66 @@
1
+ import { and, desc, eq, gt, isNull, or, sql } from 'drizzle-orm';
2
+ import db from '../../db/index.js';
3
+ import { notification, notificationRecipient } from '../../db/schema.js';
4
+ import { MAX_PAGE_SIZE } from './helpers.js';
5
+
6
+ /*
7
+ One person's notifications, most recently raised first. Dismissed and expired ones are left out. `total`
8
+ counts what the filter matches, `unread` counts every unread one whatever the filter, so a bell badge
9
+ and a paginated list can come from the same call.
10
+ */
11
+ export default async ({ userId, unreadOnly = false, limit = 20, offset = 0 } = {}) => {
12
+ if(!userId){
13
+ return [{ code: 400, msg: 'User ID is required' }, null];
14
+ }
15
+
16
+ const pageSize = Math.min(Math.max(Number.parseInt(limit, 10) || 20, 1), MAX_PAGE_SIZE);
17
+ const skip = Math.max(Number.parseInt(offset, 10) || 0, 0);
18
+
19
+ try {
20
+ const now = new Date();
21
+ const visible = and(
22
+ eq(notificationRecipient.userId, userId),
23
+ isNull(notificationRecipient.dismissedAt),
24
+ or(isNull(notification.expiresAt), gt(notification.expiresAt, now))
25
+ );
26
+
27
+ const rows = await db
28
+ .select({
29
+ id: notification.id,
30
+ owner: notification.owner,
31
+ title: notification.title,
32
+ message: notification.message,
33
+ level: notification.level,
34
+ actions: notification.actions,
35
+ createdAt: notification.createdAt,
36
+ updatedAt: notification.updatedAt,
37
+ readAt: notificationRecipient.readAt,
38
+ handledAt: notificationRecipient.handledAt
39
+ })
40
+ .from(notificationRecipient)
41
+ .innerJoin(notification, eq(notification.id, notificationRecipient.notificationId))
42
+ .where(unreadOnly ? and(visible, isNull(notificationRecipient.readAt)) : visible)
43
+ .orderBy(desc(notification.updatedAt), desc(notification.id))
44
+ .limit(pageSize)
45
+ .offset(skip);
46
+
47
+ const [counts] = await db
48
+ .select({
49
+ all: sql`count(*)`.mapWith(Number),
50
+ unread: sql`count(*) filter (where ${notificationRecipient.readAt} is null)`.mapWith(Number)
51
+ })
52
+ .from(notificationRecipient)
53
+ .innerJoin(notification, eq(notification.id, notificationRecipient.notificationId))
54
+ .where(visible);
55
+
56
+ return [null, {
57
+ notifications: rows,
58
+ total: unreadOnly ? counts.unread : counts.all,
59
+ unread: counts.unread,
60
+ limit: pageSize,
61
+ offset: skip
62
+ }];
63
+ } catch(error) {
64
+ return [{ code: 500, msg: 'Failed to retrieve notifications' }, null];
65
+ }
66
+ };
@@ -0,0 +1,26 @@
1
+ import { and, eq, gt, isNull, or, sql } from 'drizzle-orm';
2
+ import db from '../../db/index.js';
3
+ import { notification, notificationRecipient } from '../../db/schema.js';
4
+
5
+ export default async ({ userId } = {}) => {
6
+ if(!userId){
7
+ return [{ code: 400, msg: 'User ID is required' }, null];
8
+ }
9
+
10
+ try {
11
+ const [{ count }] = await db
12
+ .select({ count: sql`count(*)`.mapWith(Number) })
13
+ .from(notificationRecipient)
14
+ .innerJoin(notification, eq(notification.id, notificationRecipient.notificationId))
15
+ .where(and(
16
+ eq(notificationRecipient.userId, userId),
17
+ isNull(notificationRecipient.readAt),
18
+ isNull(notificationRecipient.dismissedAt),
19
+ or(isNull(notification.expiresAt), gt(notification.expiresAt, new Date()))
20
+ ));
21
+
22
+ return [null, { count }];
23
+ } catch(error) {
24
+ return [{ code: 500, msg: 'Failed to count unread notifications' }, null];
25
+ }
26
+ };
@@ -0,0 +1,94 @@
1
+ export const LEVELS = ['info', 'success', 'warning', 'error'];
2
+ export const API_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE'];
3
+ export const MAX_ACTIONS = 3;
4
+ export const MAX_TITLE_LENGTH = 200;
5
+ export const MAX_MESSAGE_LENGTH = 2000;
6
+ export const MAX_LABEL_LENGTH = 40;
7
+ export const MAX_URL_LENGTH = 2048;
8
+ export const MAX_BODY_BYTES = 4096;
9
+ export const DEFAULT_RETENTION_DAYS = 90;
10
+ export const MAX_PAGE_SIZE = 100;
11
+
12
+ /*
13
+ A path on this site and nothing else. It must begin with exactly one slash: `//host` and `/\\host`
14
+ are read by browsers as another origin, and `javascript:` or `https:` URLs do not begin with a slash
15
+ at all. Whitespace, backslashes and control characters are refused because browsers strip or
16
+ rewrite some of them before resolving, which is how a path that looks safe becomes an external one.
17
+ */
18
+ export const isLocalPath = value =>
19
+ typeof value === 'string' &&
20
+ value.length <= MAX_URL_LENGTH &&
21
+ /^\/(?![/\\])[^\\\s\u0000-\u001f\u007f]*$/.test(value);
22
+
23
+ /*
24
+ Returns the actions reduced to the known fields, so nothing a caller passes beyond them is stored.
25
+ An action is either a link (`href`) or a call (`api`), never both.
26
+ */
27
+ export const validateActions = actions => {
28
+ if(actions === undefined || actions === null){
29
+ return [null, []];
30
+ }
31
+
32
+ if(!Array.isArray(actions) || actions.length > MAX_ACTIONS){
33
+ return [{ code: 400, msg: `actions must be an array of at most ${MAX_ACTIONS} items` }, null];
34
+ }
35
+
36
+ const clean = [];
37
+
38
+ for(const action of actions){
39
+ if(!action || typeof action !== 'object' || Array.isArray(action)){
40
+ return [{ code: 400, msg: 'Each action must be an object' }, null];
41
+ }
42
+
43
+ const { label, href, api } = action;
44
+
45
+ if(typeof label !== 'string' || !label.trim() || label.length > MAX_LABEL_LENGTH){
46
+ return [{ code: 400, msg: `Each action needs a label of at most ${MAX_LABEL_LENGTH} characters` }, null];
47
+ }
48
+
49
+ if((href === undefined) === (api === undefined)){
50
+ return [{ code: 400, msg: 'Each action needs exactly one of href or api' }, null];
51
+ }
52
+
53
+ if(href !== undefined){
54
+ if(!isLocalPath(href)){
55
+ return [{ code: 400, msg: 'An action href must be a path on this site, beginning with a single "/"' }, null];
56
+ }
57
+ clean.push({ label: label.trim(), href });
58
+ continue;
59
+ }
60
+
61
+ if(!api || typeof api !== 'object' || Array.isArray(api)){
62
+ return [{ code: 400, msg: 'An action api must be an object with a method and url' }, null];
63
+ }
64
+
65
+ const method = typeof api.method === 'string' ? api.method.toUpperCase() : '';
66
+
67
+ if(!API_METHODS.includes(method)){
68
+ return [{ code: 400, msg: `An action api method must be one of ${API_METHODS.join(', ')}` }, null];
69
+ }
70
+
71
+ if(!isLocalPath(api.url)){
72
+ return [{ code: 400, msg: 'An action api url must be a path on this site, beginning with a single "/"' }, null];
73
+ }
74
+
75
+ const cleanApi = { method, url: api.url };
76
+
77
+ if(api.body !== undefined){
78
+ let serialized;
79
+ try {
80
+ serialized = JSON.stringify(api.body);
81
+ } catch {
82
+ serialized = undefined;
83
+ }
84
+ if(serialized === undefined || Buffer.byteLength(serialized) > MAX_BODY_BYTES){
85
+ return [{ code: 400, msg: `An action api body must be JSON of at most ${MAX_BODY_BYTES} bytes` }, null];
86
+ }
87
+ cleanApi.body = JSON.parse(serialized);
88
+ }
89
+
90
+ clean.push({ label: label.trim(), api: cleanApi });
91
+ }
92
+
93
+ return [null, clean];
94
+ };
@@ -0,0 +1,21 @@
1
+ import { and, eq, isNull } from 'drizzle-orm';
2
+ import db from '../../db/index.js';
3
+ import { notificationRecipient } from '../../db/schema.js';
4
+
5
+ export default async ({ userId } = {}) => {
6
+ if(!userId){
7
+ return [{ code: 400, msg: 'User ID is required' }, null];
8
+ }
9
+
10
+ try {
11
+ const updated = await db
12
+ .update(notificationRecipient)
13
+ .set({ readAt: new Date() })
14
+ .where(and(eq(notificationRecipient.userId, userId), isNull(notificationRecipient.readAt), isNull(notificationRecipient.dismissedAt)))
15
+ .returning({ id: notificationRecipient.notificationId });
16
+
17
+ return [null, { updated: updated.length }];
18
+ } catch(error) {
19
+ return [{ code: 500, msg: 'Failed to mark notifications as read' }, null];
20
+ }
21
+ };
@@ -0,0 +1,39 @@
1
+ import { and, eq, inArray, sql } from 'drizzle-orm';
2
+ import db from '../../db/index.js';
3
+ import { notification, notificationRecipient } from '../../db/schema.js';
4
+
5
+ /*
6
+ Records that the action on a notification was taken, which also reads it. Names the notification by
7
+ `notificationId`, or by `owner` and `dedupeKey` so the extension that raised it can resolve it without
8
+ having kept the id; `userId` limits it to one person, and without it every recipient is updated.
9
+ */
10
+ export default async ({ userId, notificationId, owner, dedupeKey } = {}) => {
11
+ if(!notificationId && !(owner && dedupeKey)){
12
+ return [{ code: 400, msg: 'Notification ID, or owner and dedupeKey, is required' }, null];
13
+ }
14
+
15
+ try {
16
+ const target = notificationId
17
+ ? eq(notificationRecipient.notificationId, notificationId)
18
+ : inArray(notificationRecipient.notificationId, db.select({ id: notification.id }).from(notification).where(and(eq(notification.owner, owner), eq(notification.dedupeKey, dedupeKey))));
19
+
20
+ const now = new Date().toISOString();
21
+
22
+ const updated = await db
23
+ .update(notificationRecipient)
24
+ .set({
25
+ handledAt: sql`coalesce(${notificationRecipient.handledAt}, ${now}::timestamp)`,
26
+ readAt: sql`coalesce(${notificationRecipient.readAt}, ${now}::timestamp)`
27
+ })
28
+ .where(userId ? and(target, eq(notificationRecipient.userId, userId)) : target)
29
+ .returning({ id: notificationRecipient.notificationId });
30
+
31
+ if(!updated.length && notificationId){
32
+ return [{ code: 404, msg: 'Notification not found' }, null];
33
+ }
34
+
35
+ return [null, { updated: updated.length }];
36
+ } catch(error) {
37
+ return [{ code: 500, msg: 'Failed to mark notification as handled' }, null];
38
+ }
39
+ };
@@ -0,0 +1,26 @@
1
+ import { and, eq, sql } from 'drizzle-orm';
2
+ import db from '../../db/index.js';
3
+ import { notificationRecipient } from '../../db/schema.js';
4
+
5
+ /* Marking an already read notification read keeps its original time. */
6
+ export default async ({ userId, notificationId } = {}) => {
7
+ if(!userId || !notificationId){
8
+ return [{ code: 400, msg: 'User ID and notification ID are required' }, null];
9
+ }
10
+
11
+ try {
12
+ const updated = await db
13
+ .update(notificationRecipient)
14
+ .set({ readAt: sql`coalesce(${notificationRecipient.readAt}, ${new Date().toISOString()}::timestamp)` })
15
+ .where(and(eq(notificationRecipient.userId, userId), eq(notificationRecipient.notificationId, notificationId)))
16
+ .returning({ id: notificationRecipient.notificationId });
17
+
18
+ if(!updated.length){
19
+ return [{ code: 404, msg: 'Notification not found' }, null];
20
+ }
21
+
22
+ return [null, { id: notificationId }];
23
+ } catch(error) {
24
+ return [{ code: 500, msg: 'Failed to mark notification as read' }, null];
25
+ }
26
+ };
@@ -0,0 +1,34 @@
1
+ import { lt, lte, or, and, isNotNull } from 'drizzle-orm';
2
+ import db from '../../db/index.js';
3
+ import { notification } from '../../db/schema.js';
4
+ import getSetting from '../settings/getSetting.js';
5
+ import { DEFAULT_RETENTION_DAYS } from './helpers.js';
6
+
7
+ /*
8
+ Deletes notifications, with their per-person state, that have expired or that were last raised longer
9
+ ago than the retention. Retention is the `system:notification_retention_days` setting (default 90);
10
+ 0 keeps history forever, and an expiry set by the sender is still honoured.
11
+ */
12
+ export default async ({ now = new Date(), retentionDays } = {}) => {
13
+ try {
14
+ let days = retentionDays;
15
+
16
+ if(days === undefined){
17
+ const [, stored] = await getSetting('system', 'notification_retention_days', DEFAULT_RETENTION_DAYS);
18
+ days = Number(stored);
19
+ if(!Number.isFinite(days) || days < 0) days = DEFAULT_RETENTION_DAYS;
20
+ }
21
+
22
+ const expired = and(isNotNull(notification.expiresAt), lte(notification.expiresAt, now));
23
+ const stale = days > 0 ? lt(notification.updatedAt, new Date(now.getTime() - days * 86400000)) : undefined;
24
+
25
+ const removed = await db
26
+ .delete(notification)
27
+ .where(stale ? or(expired, stale) : expired)
28
+ .returning({ id: notification.id });
29
+
30
+ return [null, { deleted: removed.length }];
31
+ } catch(error) {
32
+ return [{ code: 500, msg: 'Failed to prune notifications' }, null];
33
+ }
34
+ };