@nuxt-customer-portal/core 0.3.0
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.
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/app/composables/useAutoPagination.ts +30 -0
- package/app/composables/useOrganization.ts +117 -0
- package/app/composables/usePaginatedResource.ts +167 -0
- package/app/composables/usePortalFeatures.ts +74 -0
- package/app/composables/usePortalSession.ts +20 -0
- package/app/composables/useUserStore.ts +460 -0
- package/app/middleware/auth.global.ts +32 -0
- package/app/plugins/auth.ts +28 -0
- package/app/plugins/portal-core-feature.ts +3 -0
- package/app/types/index.d.ts +75 -0
- package/app/utils/auth-client.ts +31 -0
- package/app/utils/permission-utils.ts +21 -0
- package/app/utils/random.ts +9 -0
- package/i18n/locales/en.json +81 -0
- package/i18n/locales/nl.json +81 -0
- package/migrations/0000_baseline.sql +119 -0
- package/migrations/0001_organization_types.sql +12 -0
- package/migrations/0002_invitation_created_at.sql +1 -0
- package/migrations/0003_system_admin_role.sql +5 -0
- package/migrations/0004_provider_organization_type.sql +16 -0
- package/migrations/0005_portal_email_settings.sql +15 -0
- package/migrations/meta/0000_snapshot.json +880 -0
- package/migrations/meta/_journal.json +13 -0
- package/nuxt.config.ts +55 -0
- package/package.json +66 -0
- package/portal.manifest.mjs +8 -0
- package/server/api/auth/[...].ts +18 -0
- package/server/api/auth/permissions.get.ts +64 -0
- package/server/api/notifications.ts +294 -0
- package/server/db/core-schema.ts +1 -0
- package/server/db/schema/auth-schema.ts +198 -0
- package/server/db/schema/index.ts +1 -0
- package/server/middleware/api-docs-auth.ts +26 -0
- package/server/middleware/auth.ts +61 -0
- package/server/portal.ts +212 -0
- package/server/routes/api-docs/openapi.json.get.ts +22 -0
- package/server/utils/admin-email-allowlist.ts +12 -0
- package/server/utils/auth.ts +326 -0
- package/server/utils/business-hooks.ts +28 -0
- package/server/utils/db.ts +18 -0
- package/server/utils/email-template.html +105 -0
- package/server/utils/email-texts.ts +110 -0
- package/server/utils/email.ts +37 -0
- package/server/utils/invitation-management.ts +29 -0
- package/server/utils/openapi-auth.ts +67 -0
- package/server/utils/openapi-contracts.ts +129 -0
- package/server/utils/openapi-order.ts +136 -0
- package/server/utils/permissions.ts +179 -0
- package/server/utils/portal-email.ts +424 -0
- package/server/utils/query-builder.ts +270 -0
- package/server/utils/route-meta.ts +28 -0
- package/shared/core-feature.ts +152 -0
- package/shared/feature-registry.ts +62 -0
- package/shared/invitation-validation.ts +8 -0
- package/shared/organization-avatar.ts +11 -0
- package/shared/permissions.ts +26 -0
- package/shared/portal-session.ts +17 -0
- package/shared/types/feature.ts +130 -0
- package/shared/types/index.ts +40 -0
- package/shared/types/sections/api.ts +134 -0
- package/shared/types/sections/auth.ts +71 -0
- package/shared/types/sections/database.ts +86 -0
- package/shared/types/sections/queryBuilder.ts +31 -0
package/nuxt.config.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
|
|
3
|
+
const envFlag = (value: string | undefined, fallback = true) => (value === undefined ? fallback : value === 'true')
|
|
4
|
+
const registrationMode = ['open', 'invitation-only', 'disabled'].includes(process.env.PORTAL_REGISTRATION_MODE || '')
|
|
5
|
+
? (process.env.PORTAL_REGISTRATION_MODE as 'open' | 'invitation-only' | 'disabled')
|
|
6
|
+
: 'open'
|
|
7
|
+
|
|
8
|
+
export default defineNuxtConfig({
|
|
9
|
+
$meta: { name: 'nuxt-customer-portal-core' },
|
|
10
|
+
compatibilityDate: '2025-10-24',
|
|
11
|
+
modules: ['@nuxtjs/i18n', '@vueuse/nuxt'],
|
|
12
|
+
i18n: {
|
|
13
|
+
defaultLocale: 'en',
|
|
14
|
+
strategy: 'no_prefix',
|
|
15
|
+
locales: [
|
|
16
|
+
{ code: 'en', file: 'en.json' },
|
|
17
|
+
{ code: 'nl', file: 'nl.json' }
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
runtimeConfig: {
|
|
21
|
+
portalEmail: {
|
|
22
|
+
templateStorage: 'assets:portal-core',
|
|
23
|
+
brandingSource: 'runtime',
|
|
24
|
+
brandName: process.env.PORTAL_EMAIL_BRAND_NAME || 'Nuxt Customer Portal',
|
|
25
|
+
brandTagline: process.env.PORTAL_EMAIL_BRAND_TAGLINE || 'Customer workspace',
|
|
26
|
+
brandLogo: process.env.PORTAL_EMAIL_BRAND_LOGO || '',
|
|
27
|
+
brandLogoLight: process.env.PORTAL_EMAIL_BRAND_LOGO_LIGHT || process.env.PORTAL_EMAIL_BRAND_LOGO || '',
|
|
28
|
+
brandLogoDark: process.env.PORTAL_EMAIL_BRAND_LOGO_DARK || process.env.PORTAL_EMAIL_BRAND_LOGO || '',
|
|
29
|
+
brandIconLight: process.env.PORTAL_EMAIL_BRAND_ICON_LIGHT || '',
|
|
30
|
+
brandIconDark: process.env.PORTAL_EMAIL_BRAND_ICON_DARK || '',
|
|
31
|
+
primaryColor: process.env.PORTAL_EMAIL_PRIMARY_COLOR || '#0ea5e9'
|
|
32
|
+
},
|
|
33
|
+
portalAuth: {
|
|
34
|
+
registrationMode,
|
|
35
|
+
githubEnabled: envFlag(process.env.PORTAL_GITHUB_ENABLED),
|
|
36
|
+
googleEnabled: envFlag(process.env.PORTAL_GOOGLE_ENABLED)
|
|
37
|
+
},
|
|
38
|
+
public: {
|
|
39
|
+
portalAuth: {
|
|
40
|
+
registrationMode,
|
|
41
|
+
githubEnabled: envFlag(process.env.PORTAL_GITHUB_ENABLED),
|
|
42
|
+
googleEnabled: envFlag(process.env.PORTAL_GOOGLE_ENABLED),
|
|
43
|
+
termsUrl: process.env.PORTAL_TERMS_URL || '/'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
nitro: {
|
|
48
|
+
serverAssets: [
|
|
49
|
+
{
|
|
50
|
+
baseName: 'portal-core',
|
|
51
|
+
dir: fileURLToPath(new URL('./server/utils', import.meta.url))
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nuxt-customer-portal/core",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"homepage": "https://nuxt-customer-portal.com",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/ludulicious/nuxt-customer-portal.git"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./nuxt.config.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
"app",
|
|
16
|
+
"i18n",
|
|
17
|
+
"migrations",
|
|
18
|
+
"server",
|
|
19
|
+
"shared",
|
|
20
|
+
"nuxt.config.ts",
|
|
21
|
+
"portal.manifest.mjs",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./nuxt.config.ts",
|
|
26
|
+
"./feature": "./shared/types/feature.ts",
|
|
27
|
+
"./server": "./server/portal.ts",
|
|
28
|
+
"./schema": "./server/db/schema/index.ts",
|
|
29
|
+
"./route-meta": "./server/utils/route-meta.ts",
|
|
30
|
+
"./portal-manifest": "./portal.manifest.mjs",
|
|
31
|
+
"./app/types": "./app/types/index.d.ts",
|
|
32
|
+
"./app/*": "./app/*.ts",
|
|
33
|
+
"./server/*": "./server/*.ts",
|
|
34
|
+
"./shared/*": "./shared/*.ts"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@internationalized/date": "^3.10.0",
|
|
41
|
+
"@nuxtjs/i18n": "^10.6.0",
|
|
42
|
+
"@pinia/nuxt": "^1.0.1",
|
|
43
|
+
"@standard-schema/spec": "^1.1.0",
|
|
44
|
+
"@types/pg": "8.15.5",
|
|
45
|
+
"@vueuse/core": "^14.4.0",
|
|
46
|
+
"@vueuse/nuxt": "^14.4.0",
|
|
47
|
+
"better-auth": "1.6.25",
|
|
48
|
+
"date-fns": "^4.4.0",
|
|
49
|
+
"drizzle-orm": "^0.45.2",
|
|
50
|
+
"h3": "^1.15.4",
|
|
51
|
+
"nanoid": "^5.1.6",
|
|
52
|
+
"pg": "^8.22.0",
|
|
53
|
+
"pinia": "^4.0.2",
|
|
54
|
+
"resend": "^6.18.1",
|
|
55
|
+
"zod": "^4.4.3",
|
|
56
|
+
"@nuxt/ui": "^4.10.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"nuxt": "^4.5.1",
|
|
60
|
+
"vue": "^3.5.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"nuxt": "^4.5.1",
|
|
64
|
+
"vue": "^3.5.0"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { auth } from '@nuxt-customer-portal/core/server/utils/auth'
|
|
2
|
+
import { toWebRequest } from 'h3'
|
|
3
|
+
|
|
4
|
+
defineRouteMeta({
|
|
5
|
+
openAPI: {
|
|
6
|
+
tags: ['Authentication'],
|
|
7
|
+
operationId: 'generalAuthAllAll',
|
|
8
|
+
summary: 'Handle an authentication request',
|
|
9
|
+
description:
|
|
10
|
+
'Better Auth catch-all transport. Concrete authentication routes are documented separately from Better Auth’s generated OpenAPI contract.',
|
|
11
|
+
'x-scalar-ignore': true
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export default defineEventHandler(async (event) => {
|
|
16
|
+
// The auth.handler expects a standard Request object
|
|
17
|
+
return auth.handler(toWebRequest(event))
|
|
18
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { defineEventHandler, createError } from 'h3'
|
|
2
|
+
import { statement } from '@nuxt-customer-portal/core/shared/permissions'
|
|
3
|
+
import { auth } from '@nuxt-customer-portal/core/server/utils/auth'
|
|
4
|
+
import { getUserPermissions } from '@nuxt-customer-portal/core/server/utils/permissions'
|
|
5
|
+
|
|
6
|
+
defineRouteMeta({
|
|
7
|
+
openAPI: {
|
|
8
|
+
tags: ['General'],
|
|
9
|
+
operationId: 'generalAuthPermissionsGet',
|
|
10
|
+
summary: 'Get current user permissions',
|
|
11
|
+
description:
|
|
12
|
+
'Get current user permissions. Uses the current authenticated session and enforces the relevant portal permissions.'
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export default defineEventHandler(async (event) => {
|
|
17
|
+
// Get authenticated user
|
|
18
|
+
const session = await auth.api.getSession({ headers: event.headers })
|
|
19
|
+
if (!session?.user) {
|
|
20
|
+
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type SessionUserWithRole = { role?: string }
|
|
24
|
+
const currentUser = session.user as SessionUserWithRole
|
|
25
|
+
const currentRole = currentUser.role || 'user'
|
|
26
|
+
|
|
27
|
+
// Check organization role for both "user" and "admin" roles if activeOrganizationId exists
|
|
28
|
+
// auth.api.getSession() returns { user, session } where session has activeOrganizationId
|
|
29
|
+
type SessionWithOrg = { session?: { activeOrganizationId?: string }; activeOrganizationId?: string }
|
|
30
|
+
const sessionWithOrg = session as SessionWithOrg
|
|
31
|
+
const activeOrganizationId = sessionWithOrg?.session?.activeOrganizationId || sessionWithOrg?.activeOrganizationId
|
|
32
|
+
|
|
33
|
+
// Get user permissions using the shared utility
|
|
34
|
+
const {
|
|
35
|
+
permissions: rolePermissions,
|
|
36
|
+
orgRole,
|
|
37
|
+
organization: activeOrganization
|
|
38
|
+
} = await getUserPermissions(session.user.id, currentRole, activeOrganizationId || undefined)
|
|
39
|
+
|
|
40
|
+
// Get all permissions for the user
|
|
41
|
+
const permissions: Record<string, boolean> = {}
|
|
42
|
+
|
|
43
|
+
// Check each subject and permission combination
|
|
44
|
+
for (const [subject, actions] of Object.entries(statement)) {
|
|
45
|
+
if (Array.isArray(actions)) {
|
|
46
|
+
// Get permissions for this subject from the role
|
|
47
|
+
const rolePermissionsForSubject = rolePermissions[subject] || []
|
|
48
|
+
|
|
49
|
+
for (const action of actions) {
|
|
50
|
+
// Check if the role has this permission
|
|
51
|
+
permissions[`${subject}.${action}`] =
|
|
52
|
+
Array.isArray(rolePermissionsForSubject) && rolePermissionsForSubject.includes(action)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
permissions,
|
|
59
|
+
role: currentRole,
|
|
60
|
+
organizationRole: orgRole || null,
|
|
61
|
+
activeOrganization: activeOrganization,
|
|
62
|
+
organizationType: activeOrganization?.organizationType ?? null
|
|
63
|
+
}
|
|
64
|
+
})
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { sub } from 'date-fns'
|
|
2
|
+
|
|
3
|
+
defineRouteMeta({
|
|
4
|
+
openAPI: {
|
|
5
|
+
tags: ['General'],
|
|
6
|
+
operationId: 'generalNotificationsAll',
|
|
7
|
+
summary: 'List sample notifications',
|
|
8
|
+
description:
|
|
9
|
+
'List sample notifications. Uses the current authenticated session and enforces the relevant portal permissions.'
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const notifications = [
|
|
14
|
+
{
|
|
15
|
+
id: 1,
|
|
16
|
+
unread: true,
|
|
17
|
+
sender: {
|
|
18
|
+
name: 'Jordan Brown',
|
|
19
|
+
email: 'jordan.brown@example.com',
|
|
20
|
+
avatar: {
|
|
21
|
+
src: 'https://i.pravatar.cc/128?u=2'
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
body: 'sent you a message',
|
|
25
|
+
date: sub(new Date(), { minutes: 7 }).toISOString()
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 2,
|
|
29
|
+
sender: {
|
|
30
|
+
name: 'Lindsay Walton'
|
|
31
|
+
},
|
|
32
|
+
body: 'subscribed to your email list',
|
|
33
|
+
date: sub(new Date(), { hours: 1 }).toISOString()
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 3,
|
|
37
|
+
unread: true,
|
|
38
|
+
sender: {
|
|
39
|
+
name: 'Taylor Green',
|
|
40
|
+
email: 'taylor.green@example.com',
|
|
41
|
+
avatar: {
|
|
42
|
+
src: 'https://i.pravatar.cc/128?u=3'
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
body: 'sent you a message',
|
|
46
|
+
date: sub(new Date(), { hours: 3 }).toISOString()
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 4,
|
|
50
|
+
sender: {
|
|
51
|
+
name: 'Courtney Henry',
|
|
52
|
+
avatar: {
|
|
53
|
+
src: 'https://i.pravatar.cc/128?u=4'
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
body: 'added you to a project',
|
|
57
|
+
date: sub(new Date(), { hours: 3 }).toISOString()
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 5,
|
|
61
|
+
sender: {
|
|
62
|
+
name: 'Tom Cook',
|
|
63
|
+
avatar: {
|
|
64
|
+
src: 'https://i.pravatar.cc/128?u=5'
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
body: 'abandonned cart',
|
|
68
|
+
date: sub(new Date(), { hours: 7 }).toISOString()
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 6,
|
|
72
|
+
sender: {
|
|
73
|
+
name: 'Casey Thomas',
|
|
74
|
+
avatar: {
|
|
75
|
+
src: 'https://i.pravatar.cc/128?u=6'
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
body: 'purchased your product',
|
|
79
|
+
date: sub(new Date(), { days: 1, hours: 3 }).toISOString()
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 7,
|
|
83
|
+
unread: true,
|
|
84
|
+
sender: {
|
|
85
|
+
name: 'Kelly Wilson',
|
|
86
|
+
email: 'kelly.wilson@example.com',
|
|
87
|
+
avatar: {
|
|
88
|
+
src: 'https://i.pravatar.cc/128?u=8'
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
body: 'sent you a message',
|
|
92
|
+
date: sub(new Date(), { days: 2 }).toISOString()
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 8,
|
|
96
|
+
sender: {
|
|
97
|
+
name: 'Jamie Johnson',
|
|
98
|
+
email: 'jamie.johnson@example.com',
|
|
99
|
+
avatar: {
|
|
100
|
+
src: 'https://i.pravatar.cc/128?u=9'
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
body: 'requested a refund',
|
|
104
|
+
date: sub(new Date(), { days: 5, hours: 4 }).toISOString()
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 9,
|
|
108
|
+
unread: true,
|
|
109
|
+
sender: {
|
|
110
|
+
name: 'Morgan Anderson',
|
|
111
|
+
email: 'morgan.anderson@example.com'
|
|
112
|
+
},
|
|
113
|
+
body: 'sent you a message',
|
|
114
|
+
date: sub(new Date(), { days: 6 }).toISOString()
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 10,
|
|
118
|
+
sender: {
|
|
119
|
+
name: 'Drew Moore'
|
|
120
|
+
},
|
|
121
|
+
body: 'subscribed to your email list',
|
|
122
|
+
date: sub(new Date(), { days: 6 }).toISOString()
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 11,
|
|
126
|
+
sender: {
|
|
127
|
+
name: 'Riley Davis'
|
|
128
|
+
},
|
|
129
|
+
body: 'abandonned cart',
|
|
130
|
+
date: sub(new Date(), { days: 7 }).toISOString()
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 12,
|
|
134
|
+
sender: {
|
|
135
|
+
name: 'Jordan Taylor'
|
|
136
|
+
},
|
|
137
|
+
body: 'subscribed to your email list',
|
|
138
|
+
date: sub(new Date(), { days: 9 }).toISOString()
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 13,
|
|
142
|
+
sender: {
|
|
143
|
+
name: 'Kelly Wilson',
|
|
144
|
+
email: 'kelly.wilson@example.com',
|
|
145
|
+
avatar: {
|
|
146
|
+
src: 'https://i.pravatar.cc/128?u=8'
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
body: 'subscribed to your email list',
|
|
150
|
+
date: sub(new Date(), { days: 10 }).toISOString()
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 14,
|
|
154
|
+
sender: {
|
|
155
|
+
name: 'Jamie Johnson',
|
|
156
|
+
email: 'jamie.johnson@example.com',
|
|
157
|
+
avatar: {
|
|
158
|
+
src: 'https://i.pravatar.cc/128?u=9'
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
body: 'subscribed to your email list',
|
|
162
|
+
date: sub(new Date(), { days: 11 }).toISOString()
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: 15,
|
|
166
|
+
sender: {
|
|
167
|
+
name: 'Morgan Anderson'
|
|
168
|
+
},
|
|
169
|
+
body: 'purchased your product',
|
|
170
|
+
date: sub(new Date(), { days: 12 }).toISOString()
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 16,
|
|
174
|
+
sender: {
|
|
175
|
+
name: 'Drew Moore',
|
|
176
|
+
avatar: {
|
|
177
|
+
src: 'https://i.pravatar.cc/128?u=16'
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
body: 'subscribed to your email list',
|
|
181
|
+
date: sub(new Date(), { days: 13 }).toISOString()
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: 17,
|
|
185
|
+
sender: {
|
|
186
|
+
name: 'Riley Davis'
|
|
187
|
+
},
|
|
188
|
+
body: 'subscribed to your email list',
|
|
189
|
+
date: sub(new Date(), { days: 14 }).toISOString()
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: 18,
|
|
193
|
+
sender: {
|
|
194
|
+
name: 'Jordan Taylor'
|
|
195
|
+
},
|
|
196
|
+
body: 'subscribed to your email list',
|
|
197
|
+
date: sub(new Date(), { days: 15 }).toISOString()
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 19,
|
|
201
|
+
sender: {
|
|
202
|
+
name: 'Kelly Wilson',
|
|
203
|
+
email: 'kelly.wilson@example.com',
|
|
204
|
+
avatar: {
|
|
205
|
+
src: 'https://i.pravatar.cc/128?u=8'
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
body: 'subscribed to your email list',
|
|
209
|
+
date: sub(new Date(), { days: 16 }).toISOString()
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: 20,
|
|
213
|
+
sender: {
|
|
214
|
+
name: 'Jamie Johnson',
|
|
215
|
+
email: 'jamie.johnson@example.com',
|
|
216
|
+
avatar: {
|
|
217
|
+
src: 'https://i.pravatar.cc/128?u=9'
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
body: 'purchased your product',
|
|
221
|
+
date: sub(new Date(), { days: 17 }).toISOString()
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: 21,
|
|
225
|
+
sender: {
|
|
226
|
+
name: 'Morgan Anderson'
|
|
227
|
+
},
|
|
228
|
+
body: 'abandonned cart',
|
|
229
|
+
date: sub(new Date(), { days: 17 }).toISOString()
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: 22,
|
|
233
|
+
sender: {
|
|
234
|
+
name: 'Drew Moore'
|
|
235
|
+
},
|
|
236
|
+
body: 'subscribed to your email list',
|
|
237
|
+
date: sub(new Date(), { days: 18 }).toISOString()
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: 23,
|
|
241
|
+
sender: {
|
|
242
|
+
name: 'Riley Davis'
|
|
243
|
+
},
|
|
244
|
+
body: 'subscribed to your email list',
|
|
245
|
+
date: sub(new Date(), { days: 19 }).toISOString()
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: 24,
|
|
249
|
+
sender: {
|
|
250
|
+
name: 'Jordan Taylor',
|
|
251
|
+
avatar: {
|
|
252
|
+
src: 'https://i.pravatar.cc/128?u=24'
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
body: 'subscribed to your email list',
|
|
256
|
+
date: sub(new Date(), { days: 20 }).toISOString()
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: 25,
|
|
260
|
+
sender: {
|
|
261
|
+
name: 'Kelly Wilson',
|
|
262
|
+
email: 'kelly.wilson@example.com',
|
|
263
|
+
avatar: {
|
|
264
|
+
src: 'https://i.pravatar.cc/128?u=8'
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
body: 'subscribed to your email list',
|
|
268
|
+
date: sub(new Date(), { days: 20 }).toISOString()
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: 26,
|
|
272
|
+
sender: {
|
|
273
|
+
name: 'Jamie Johnson',
|
|
274
|
+
email: 'jamie.johnson@example.com',
|
|
275
|
+
avatar: {
|
|
276
|
+
src: 'https://i.pravatar.cc/128?u=9'
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
body: 'abandonned cart',
|
|
280
|
+
date: sub(new Date(), { days: 21 }).toISOString()
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: 27,
|
|
284
|
+
sender: {
|
|
285
|
+
name: 'Morgan Anderson'
|
|
286
|
+
},
|
|
287
|
+
body: 'subscribed to your email list',
|
|
288
|
+
date: sub(new Date(), { days: 22 }).toISOString()
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
export default eventHandler(async () => {
|
|
293
|
+
return notifications
|
|
294
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { organization, user } from './schema/auth-schema'
|