@murumets-ee/create 0.1.15 → 0.1.17
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/dist/cli.mjs +245 -50
- package/dist/index.mjs +245 -50
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1250,6 +1250,231 @@ export async function POST(req: Request): Promise<Response> {
|
|
|
1250
1250
|
|
|
1251
1251
|
return handleInboundWebhook(req, (type, payload) => queueClient.enqueue(type, payload))
|
|
1252
1252
|
}
|
|
1253
|
+
`),await h(r(e,`app/[locale]/(shell)/admin/seed/actions.ts`),`'use server'
|
|
1254
|
+
|
|
1255
|
+
import { AdminClient } from '@murumets-ee/entity/admin'
|
|
1256
|
+
import { createTaxonomyClient } from '@murumets-ee/taxonomy/client'
|
|
1257
|
+
import { Ticket, TicketMessage, Department, TicketTag } from '@murumets-ee/ticketing'
|
|
1258
|
+
import { headers } from 'next/headers'
|
|
1259
|
+
import { Article, Category } from '@/entities'
|
|
1260
|
+
import { getToolkitApp } from '@/lib/app'
|
|
1261
|
+
import { auth } from '@/lib/auth'
|
|
1262
|
+
|
|
1263
|
+
async function requireAdmin() {
|
|
1264
|
+
const session = await auth.api.getSession({ headers: await headers() })
|
|
1265
|
+
if (!session?.user) throw new Error('Unauthorized')
|
|
1266
|
+
const role = (session.user as Record<string, unknown>).role as string | undefined
|
|
1267
|
+
if (role !== 'admin') throw new Error('Forbidden: admin role required')
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
export async function seedContent() {
|
|
1271
|
+
if (process.env.NODE_ENV === 'production') {
|
|
1272
|
+
return { error: 'Seed disabled in production' }
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
await requireAdmin()
|
|
1276
|
+
const app = await getToolkitApp()
|
|
1277
|
+
const categories = createTaxonomyClient(Category)
|
|
1278
|
+
const articles = new AdminClient({
|
|
1279
|
+
entity: Article,
|
|
1280
|
+
db: app.db.readWrite,
|
|
1281
|
+
logger: app.logger.child({ entity: 'article' }),
|
|
1282
|
+
})
|
|
1283
|
+
|
|
1284
|
+
const techCat = await categories.create({ name: 'Technology', slug: 'technology', color: '#3B82F6' })
|
|
1285
|
+
const newsCat = await categories.create({ name: 'News', slug: 'news', color: '#EF4444' })
|
|
1286
|
+
|
|
1287
|
+
await articles.create({
|
|
1288
|
+
title: 'Getting Started with Lumi CMS',
|
|
1289
|
+
slug: 'getting-started',
|
|
1290
|
+
excerpt: 'Learn how to build apps with the toolkit',
|
|
1291
|
+
body: [{ type: 'p', children: [{ text: 'A guide to getting started with Lumi CMS.' }] }],
|
|
1292
|
+
category: techCat.id as string,
|
|
1293
|
+
status: 'published',
|
|
1294
|
+
publishedAt: new Date(),
|
|
1295
|
+
})
|
|
1296
|
+
|
|
1297
|
+
await articles.create({
|
|
1298
|
+
title: 'Lumi CMS v1.0 Released',
|
|
1299
|
+
slug: 'v1-released',
|
|
1300
|
+
excerpt: 'We are excited to announce version 1.0',
|
|
1301
|
+
body: [{ type: 'p', children: [{ text: 'After months of development, v1.0 is here.' }] }],
|
|
1302
|
+
category: newsCat.id as string,
|
|
1303
|
+
status: 'published',
|
|
1304
|
+
publishedAt: new Date(),
|
|
1305
|
+
})
|
|
1306
|
+
|
|
1307
|
+
return { ok: true, created: { categories: 2, articles: 2 } }
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
export async function seedTicketing() {
|
|
1311
|
+
if (process.env.NODE_ENV === 'production') {
|
|
1312
|
+
return { error: 'Seed disabled in production' }
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
await requireAdmin()
|
|
1316
|
+
const app = await getToolkitApp()
|
|
1317
|
+
|
|
1318
|
+
const deptClient = createTaxonomyClient(Department)
|
|
1319
|
+
const tagClient = createTaxonomyClient(TicketTag)
|
|
1320
|
+
const ticketAdmin = new AdminClient({
|
|
1321
|
+
entity: Ticket,
|
|
1322
|
+
db: app.db.readWrite,
|
|
1323
|
+
logger: app.logger.child({ entity: 'ticket' }),
|
|
1324
|
+
})
|
|
1325
|
+
const messageAdmin = new AdminClient({
|
|
1326
|
+
entity: TicketMessage,
|
|
1327
|
+
db: app.db.readWrite,
|
|
1328
|
+
logger: app.logger.child({ entity: 'ticket_message' }),
|
|
1329
|
+
})
|
|
1330
|
+
|
|
1331
|
+
const support = await deptClient.create({
|
|
1332
|
+
name: 'General Support', slug: 'general-support', color: '#3B82F6',
|
|
1333
|
+
description: 'General customer support inquiries',
|
|
1334
|
+
emailAddress: 'support@example.com',
|
|
1335
|
+
slaFirstResponseHours: 4, slaResolutionHours: 24,
|
|
1336
|
+
})
|
|
1337
|
+
const billing = await deptClient.create({
|
|
1338
|
+
name: 'Billing', slug: 'billing', color: '#10B981',
|
|
1339
|
+
description: 'Payment and invoice questions',
|
|
1340
|
+
emailAddress: 'billing@example.com',
|
|
1341
|
+
slaFirstResponseHours: 2, slaResolutionHours: 12,
|
|
1342
|
+
})
|
|
1343
|
+
const technical = await deptClient.create({
|
|
1344
|
+
name: 'Technical', slug: 'technical', color: '#8B5CF6',
|
|
1345
|
+
description: 'Technical issues and bug reports',
|
|
1346
|
+
emailAddress: 'tech@example.com',
|
|
1347
|
+
slaFirstResponseHours: 1, slaResolutionHours: 8,
|
|
1348
|
+
})
|
|
1349
|
+
|
|
1350
|
+
const bugTag = await tagClient.create({ name: 'Bug', slug: 'bug', color: '#EF4444' })
|
|
1351
|
+
const featureTag = await tagClient.create({ name: 'Feature Request', slug: 'feature-request', color: '#F59E0B' })
|
|
1352
|
+
const urgentTag = await tagClient.create({ name: 'Urgent', slug: 'urgent', color: '#DC2626' })
|
|
1353
|
+
await tagClient.create({ name: 'Feedback', slug: 'feedback', color: '#06B6D4' })
|
|
1354
|
+
await tagClient.create({ name: 'Documentation', slug: 'documentation', color: '#6366F1' })
|
|
1355
|
+
|
|
1356
|
+
const now = Date.now()
|
|
1357
|
+
let counter = 0
|
|
1358
|
+
|
|
1359
|
+
async function createTicket(opts: {
|
|
1360
|
+
subject: string; department: string; status: string; priority: string;
|
|
1361
|
+
requesterEmail: string; requesterName: string; tags?: string[];
|
|
1362
|
+
messages: Array<{ body: string; senderType: string; senderEmail: string; senderName: string; visibility?: string; minutesAgo: number }>
|
|
1363
|
+
}) {
|
|
1364
|
+
counter++
|
|
1365
|
+
const lastMsg = opts.messages[opts.messages.length - 1]
|
|
1366
|
+
const ticket = await ticketAdmin.create({
|
|
1367
|
+
subject: opts.subject, status: opts.status, priority: opts.priority,
|
|
1368
|
+
department: opts.department, requesterEmail: opts.requesterEmail,
|
|
1369
|
+
requesterName: opts.requesterName,
|
|
1370
|
+
ticketNumber: \`TK-\${String(counter).padStart(6, '0')}\`,
|
|
1371
|
+
lastReplyAt: new Date(now - lastMsg.minutesAgo * 60_000),
|
|
1372
|
+
lastReplierType: lastMsg.senderType,
|
|
1373
|
+
tags: opts.tags ?? [],
|
|
1374
|
+
})
|
|
1375
|
+
for (const msg of opts.messages) {
|
|
1376
|
+
await messageAdmin.create({
|
|
1377
|
+
ticket: ticket.id, body: msg.body, bodyFormat: 'html',
|
|
1378
|
+
senderType: msg.senderType, senderEmail: msg.senderEmail,
|
|
1379
|
+
senderName: msg.senderName, visibility: msg.visibility ?? 'public',
|
|
1380
|
+
})
|
|
1381
|
+
}
|
|
1382
|
+
return ticket
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
await createTicket({
|
|
1386
|
+
subject: 'API returns 500 error on file upload',
|
|
1387
|
+
department: technical.id as string, status: 'open', priority: 'high',
|
|
1388
|
+
requesterEmail: 'john@acme.com', requesterName: 'John Smith',
|
|
1389
|
+
tags: [bugTag.id as string, urgentTag.id as string],
|
|
1390
|
+
messages: [
|
|
1391
|
+
{ body: '<p>When I upload files larger than 5MB, I get a 500 error. Started after the last update.</p>', senderType: 'customer', senderEmail: 'john@acme.com', senderName: 'John Smith', minutesAgo: 120 },
|
|
1392
|
+
{ body: '<p>Thanks for reporting this. I can see the error in our logs. Escalating to backend team.</p>', senderType: 'agent', senderEmail: 'sarah@support.com', senderName: 'Sarah Chen', minutesAgo: 90 },
|
|
1393
|
+
{ body: '<p>Any update? We have a deadline tomorrow.</p>', senderType: 'customer', senderEmail: 'john@acme.com', senderName: 'John Smith', minutesAgo: 30 },
|
|
1394
|
+
],
|
|
1395
|
+
})
|
|
1396
|
+
|
|
1397
|
+
await createTicket({
|
|
1398
|
+
subject: 'Invoice #2024-0847 has incorrect amount',
|
|
1399
|
+
department: billing.id as string, status: 'pending', priority: 'normal',
|
|
1400
|
+
requesterEmail: 'maria@startup.io', requesterName: 'Maria Garcia',
|
|
1401
|
+
messages: [
|
|
1402
|
+
{ body: '<p>Invoice #2024-0847 shows $299 instead of the agreed $199. Please correct.</p>', senderType: 'customer', senderEmail: 'maria@startup.io', senderName: 'Maria Garcia', minutesAgo: 1440 },
|
|
1403
|
+
{ body: '<p>Apologies for the discrepancy. Checking with billing team, should be resolved in 24h.</p>', senderType: 'agent', senderEmail: 'alex@support.com', senderName: 'Alex Johnson', minutesAgo: 1380 },
|
|
1404
|
+
],
|
|
1405
|
+
})
|
|
1406
|
+
|
|
1407
|
+
await createTicket({
|
|
1408
|
+
subject: 'Production site down — 503 errors',
|
|
1409
|
+
department: technical.id as string, status: 'open', priority: 'urgent',
|
|
1410
|
+
requesterEmail: 'ops@enterprise.co', requesterName: 'Emma Wilson',
|
|
1411
|
+
tags: [bugTag.id as string, urgentTag.id as string],
|
|
1412
|
+
messages: [
|
|
1413
|
+
{ body: '<p><strong>URGENT</strong> — Production returning 503 for all users. Need immediate help.</p>', senderType: 'customer', senderEmail: 'ops@enterprise.co', senderName: 'Emma Wilson', minutesAgo: 15 },
|
|
1414
|
+
],
|
|
1415
|
+
})
|
|
1416
|
+
|
|
1417
|
+
await createTicket({
|
|
1418
|
+
subject: 'Can we get dark mode for the dashboard?',
|
|
1419
|
+
department: support.id as string, status: 'resolved', priority: 'low',
|
|
1420
|
+
requesterEmail: 'dev@techcorp.com', requesterName: 'David Lee',
|
|
1421
|
+
tags: [featureTag.id as string],
|
|
1422
|
+
messages: [
|
|
1423
|
+
{ body: '<p>Would it be possible to add dark mode? Our team works late hours.</p>', senderType: 'customer', senderEmail: 'dev@techcorp.com', senderName: 'David Lee', minutesAgo: 10080 },
|
|
1424
|
+
{ body: '<p>Dark mode just shipped! Toggle it in Settings → Appearance.</p>', senderType: 'agent', senderEmail: 'sarah@support.com', senderName: 'Sarah Chen', minutesAgo: 4320 },
|
|
1425
|
+
{ body: '<p>Perfect, exactly what we needed. Thanks!</p>', senderType: 'customer', senderEmail: 'dev@techcorp.com', senderName: 'David Lee', minutesAgo: 4200 },
|
|
1426
|
+
],
|
|
1427
|
+
})
|
|
1428
|
+
|
|
1429
|
+
return { ok: true, created: { departments: 3, tags: 5, tickets: 4, messages: 9 } }
|
|
1430
|
+
}
|
|
1431
|
+
`),await h(r(e,`app/[locale]/(shell)/admin/seed/page.tsx`),`'use client'
|
|
1432
|
+
|
|
1433
|
+
import { Button } from '@murumets-ee/ui'
|
|
1434
|
+
import { useState } from 'react'
|
|
1435
|
+
import { seedContent, seedTicketing } from './actions'
|
|
1436
|
+
|
|
1437
|
+
export default function SeedPage() {
|
|
1438
|
+
const [result, setResult] = useState<string | null>(null)
|
|
1439
|
+
const [loading, setLoading] = useState(false)
|
|
1440
|
+
|
|
1441
|
+
async function run(action: () => Promise<unknown>) {
|
|
1442
|
+
setLoading(true)
|
|
1443
|
+
setResult(null)
|
|
1444
|
+
try {
|
|
1445
|
+
const res = await action()
|
|
1446
|
+
setResult(JSON.stringify(res, null, 2))
|
|
1447
|
+
} catch (e) {
|
|
1448
|
+
setResult(\`Error: \${e instanceof Error ? e.message : String(e)}\`)
|
|
1449
|
+
} finally {
|
|
1450
|
+
setLoading(false)
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
return (
|
|
1455
|
+
<div className="max-w-2xl mx-auto p-8">
|
|
1456
|
+
<h1 className="text-2xl font-bold mb-2">Seed Data</h1>
|
|
1457
|
+
<p className="text-sm text-zinc-500 dark:text-zinc-400 mb-6">
|
|
1458
|
+
Create demo content for testing. Only available in development mode.
|
|
1459
|
+
</p>
|
|
1460
|
+
|
|
1461
|
+
<div className="flex gap-3 flex-wrap">
|
|
1462
|
+
<Button onClick={() => run(seedContent)} loading={loading}>
|
|
1463
|
+
Seed Content
|
|
1464
|
+
</Button>
|
|
1465
|
+
<Button onClick={() => run(seedTicketing)} loading={loading} variant="secondary">
|
|
1466
|
+
Seed Ticketing
|
|
1467
|
+
</Button>
|
|
1468
|
+
</div>
|
|
1469
|
+
|
|
1470
|
+
{result && (
|
|
1471
|
+
<pre className="mt-6 bg-zinc-100 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg p-4 text-sm overflow-auto">
|
|
1472
|
+
{result}
|
|
1473
|
+
</pre>
|
|
1474
|
+
)}
|
|
1475
|
+
</div>
|
|
1476
|
+
)
|
|
1477
|
+
}
|
|
1253
1478
|
`),await h(r(e,`app/api/admin/[...path]/route.ts`),`import { createAdminApiHandler } from '@murumets-ee/admin-ui/server'
|
|
1254
1479
|
import { buildPermissionChecker, buildResourceCatalog } from '@murumets-ee/auth'
|
|
1255
1480
|
import { permissionRoutes } from '@murumets-ee/auth/admin'
|
|
@@ -1343,38 +1568,20 @@ const handler = createAdminApiHandler({
|
|
|
1343
1568
|
})
|
|
1344
1569
|
|
|
1345
1570
|
export const { GET, POST, PATCH, DELETE } = handler
|
|
1346
|
-
`)}async function x(e,t){await h(r(e,`lib/auth.ts`),`import {
|
|
1347
|
-
import {
|
|
1571
|
+
`)}async function x(e,t){await h(r(e,`lib/auth.ts`),`import { getAuth } from '@murumets-ee/auth'
|
|
1572
|
+
import { getToolkitApp } from './app'
|
|
1348
1573
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
}
|
|
1574
|
+
await getToolkitApp()
|
|
1575
|
+
|
|
1576
|
+
export const auth = getAuth()
|
|
1353
1577
|
`),await h(r(e,`lib/auth-client.ts`),`import { createClient } from '@murumets-ee/auth/client'
|
|
1354
1578
|
|
|
1355
1579
|
export const authClient = createClient()
|
|
1356
1580
|
`),await h(r(e,`app/api/auth/[...all]/route.ts`),`import { toNextJsHandler } from 'better-auth/next-js'
|
|
1357
|
-
import {
|
|
1358
|
-
|
|
1359
|
-
let _handler: ReturnType<typeof toNextJsHandler> | null = null
|
|
1360
|
-
|
|
1361
|
-
async function handler() {
|
|
1362
|
-
if (!_handler) {
|
|
1363
|
-
const auth = await getAuthInstance()
|
|
1364
|
-
_handler = toNextJsHandler(auth)
|
|
1365
|
-
}
|
|
1366
|
-
return _handler
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
export async function GET(req: Request) {
|
|
1370
|
-
const h = await handler()
|
|
1371
|
-
return h.GET(req)
|
|
1372
|
-
}
|
|
1581
|
+
import { auth } from '@/lib/auth'
|
|
1373
1582
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
return h.POST(req)
|
|
1377
|
-
}
|
|
1583
|
+
const { GET, POST } = toNextJsHandler(auth)
|
|
1584
|
+
export { GET, POST }
|
|
1378
1585
|
`),await h(r(e,`app/[locale]/auth/layout.tsx`),`import { AuthProviders } from './providers'
|
|
1379
1586
|
import type { ReactNode } from 'react'
|
|
1380
1587
|
|
|
@@ -1834,6 +2041,10 @@ export const routing = defineRouting({
|
|
|
1834
2041
|
locales: ['en'],
|
|
1835
2042
|
defaultLocale: 'en',
|
|
1836
2043
|
})
|
|
2044
|
+
`),await h(r(e,`i18n/navigation.ts`),`import { createNavigation } from 'next-intl/navigation'
|
|
2045
|
+
import { routing } from './routing'
|
|
2046
|
+
|
|
2047
|
+
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing)
|
|
1837
2048
|
`),await h(r(e,`i18n/request.ts`),`import { getRequestConfig } from 'next-intl/server'
|
|
1838
2049
|
import { hasLocale } from 'next-intl'
|
|
1839
2050
|
import { routing } from './routing'
|
|
@@ -2072,13 +2283,12 @@ export async function getToolkitApp(): Promise<ToolkitApp> {
|
|
|
2072
2283
|
}
|
|
2073
2284
|
return appInstance
|
|
2074
2285
|
}
|
|
2075
|
-
`),await h(r(i,`lib/auth.ts`),`import {
|
|
2076
|
-
import {
|
|
2286
|
+
`),await h(r(i,`lib/auth.ts`),`import { getAuth } from '@murumets-ee/auth'
|
|
2287
|
+
import { getToolkitApp } from './app'
|
|
2077
2288
|
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
}
|
|
2289
|
+
await getToolkitApp()
|
|
2290
|
+
|
|
2291
|
+
export const auth = getAuth()
|
|
2082
2292
|
`),await h(r(i,`lib/auth-client.ts`),`import { createClient } from '@murumets-ee/auth/client'
|
|
2083
2293
|
|
|
2084
2294
|
export const authClient = createClient()
|
|
@@ -2508,25 +2718,10 @@ export default async function HomePage() {
|
|
|
2508
2718
|
)
|
|
2509
2719
|
}
|
|
2510
2720
|
`),await h(r(i,`app/api/auth/[...all]/route.ts`),`import { toNextJsHandler } from 'better-auth/next-js'
|
|
2511
|
-
import {
|
|
2512
|
-
|
|
2513
|
-
let _handler: ReturnType<typeof toNextJsHandler> | null = null
|
|
2721
|
+
import { auth } from '@${n}/config/auth'
|
|
2514
2722
|
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
const auth = await getAuthInstance()
|
|
2518
|
-
_handler = toNextJsHandler(auth)
|
|
2519
|
-
}
|
|
2520
|
-
return _handler
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
export async function GET(req: Request) {
|
|
2524
|
-
return (await handler()).GET(req)
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
export async function POST(req: Request) {
|
|
2528
|
-
return (await handler()).POST(req)
|
|
2529
|
-
}
|
|
2723
|
+
const { GET, POST } = toNextJsHandler(auth)
|
|
2724
|
+
export { GET, POST }
|
|
2530
2725
|
`),await h(r(i,`app/[locale]/auth/layout.tsx`),`import { AuthProviders } from './providers'
|
|
2531
2726
|
import type { ReactNode } from 'react'
|
|
2532
2727
|
|
package/dist/index.mjs
CHANGED
|
@@ -1249,6 +1249,231 @@ export async function POST(req: Request): Promise<Response> {
|
|
|
1249
1249
|
|
|
1250
1250
|
return handleInboundWebhook(req, (type, payload) => queueClient.enqueue(type, payload))
|
|
1251
1251
|
}
|
|
1252
|
+
`),await n(a(e,`app/[locale]/(shell)/admin/seed/actions.ts`),`'use server'
|
|
1253
|
+
|
|
1254
|
+
import { AdminClient } from '@murumets-ee/entity/admin'
|
|
1255
|
+
import { createTaxonomyClient } from '@murumets-ee/taxonomy/client'
|
|
1256
|
+
import { Ticket, TicketMessage, Department, TicketTag } from '@murumets-ee/ticketing'
|
|
1257
|
+
import { headers } from 'next/headers'
|
|
1258
|
+
import { Article, Category } from '@/entities'
|
|
1259
|
+
import { getToolkitApp } from '@/lib/app'
|
|
1260
|
+
import { auth } from '@/lib/auth'
|
|
1261
|
+
|
|
1262
|
+
async function requireAdmin() {
|
|
1263
|
+
const session = await auth.api.getSession({ headers: await headers() })
|
|
1264
|
+
if (!session?.user) throw new Error('Unauthorized')
|
|
1265
|
+
const role = (session.user as Record<string, unknown>).role as string | undefined
|
|
1266
|
+
if (role !== 'admin') throw new Error('Forbidden: admin role required')
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
export async function seedContent() {
|
|
1270
|
+
if (process.env.NODE_ENV === 'production') {
|
|
1271
|
+
return { error: 'Seed disabled in production' }
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
await requireAdmin()
|
|
1275
|
+
const app = await getToolkitApp()
|
|
1276
|
+
const categories = createTaxonomyClient(Category)
|
|
1277
|
+
const articles = new AdminClient({
|
|
1278
|
+
entity: Article,
|
|
1279
|
+
db: app.db.readWrite,
|
|
1280
|
+
logger: app.logger.child({ entity: 'article' }),
|
|
1281
|
+
})
|
|
1282
|
+
|
|
1283
|
+
const techCat = await categories.create({ name: 'Technology', slug: 'technology', color: '#3B82F6' })
|
|
1284
|
+
const newsCat = await categories.create({ name: 'News', slug: 'news', color: '#EF4444' })
|
|
1285
|
+
|
|
1286
|
+
await articles.create({
|
|
1287
|
+
title: 'Getting Started with Lumi CMS',
|
|
1288
|
+
slug: 'getting-started',
|
|
1289
|
+
excerpt: 'Learn how to build apps with the toolkit',
|
|
1290
|
+
body: [{ type: 'p', children: [{ text: 'A guide to getting started with Lumi CMS.' }] }],
|
|
1291
|
+
category: techCat.id as string,
|
|
1292
|
+
status: 'published',
|
|
1293
|
+
publishedAt: new Date(),
|
|
1294
|
+
})
|
|
1295
|
+
|
|
1296
|
+
await articles.create({
|
|
1297
|
+
title: 'Lumi CMS v1.0 Released',
|
|
1298
|
+
slug: 'v1-released',
|
|
1299
|
+
excerpt: 'We are excited to announce version 1.0',
|
|
1300
|
+
body: [{ type: 'p', children: [{ text: 'After months of development, v1.0 is here.' }] }],
|
|
1301
|
+
category: newsCat.id as string,
|
|
1302
|
+
status: 'published',
|
|
1303
|
+
publishedAt: new Date(),
|
|
1304
|
+
})
|
|
1305
|
+
|
|
1306
|
+
return { ok: true, created: { categories: 2, articles: 2 } }
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
export async function seedTicketing() {
|
|
1310
|
+
if (process.env.NODE_ENV === 'production') {
|
|
1311
|
+
return { error: 'Seed disabled in production' }
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
await requireAdmin()
|
|
1315
|
+
const app = await getToolkitApp()
|
|
1316
|
+
|
|
1317
|
+
const deptClient = createTaxonomyClient(Department)
|
|
1318
|
+
const tagClient = createTaxonomyClient(TicketTag)
|
|
1319
|
+
const ticketAdmin = new AdminClient({
|
|
1320
|
+
entity: Ticket,
|
|
1321
|
+
db: app.db.readWrite,
|
|
1322
|
+
logger: app.logger.child({ entity: 'ticket' }),
|
|
1323
|
+
})
|
|
1324
|
+
const messageAdmin = new AdminClient({
|
|
1325
|
+
entity: TicketMessage,
|
|
1326
|
+
db: app.db.readWrite,
|
|
1327
|
+
logger: app.logger.child({ entity: 'ticket_message' }),
|
|
1328
|
+
})
|
|
1329
|
+
|
|
1330
|
+
const support = await deptClient.create({
|
|
1331
|
+
name: 'General Support', slug: 'general-support', color: '#3B82F6',
|
|
1332
|
+
description: 'General customer support inquiries',
|
|
1333
|
+
emailAddress: 'support@example.com',
|
|
1334
|
+
slaFirstResponseHours: 4, slaResolutionHours: 24,
|
|
1335
|
+
})
|
|
1336
|
+
const billing = await deptClient.create({
|
|
1337
|
+
name: 'Billing', slug: 'billing', color: '#10B981',
|
|
1338
|
+
description: 'Payment and invoice questions',
|
|
1339
|
+
emailAddress: 'billing@example.com',
|
|
1340
|
+
slaFirstResponseHours: 2, slaResolutionHours: 12,
|
|
1341
|
+
})
|
|
1342
|
+
const technical = await deptClient.create({
|
|
1343
|
+
name: 'Technical', slug: 'technical', color: '#8B5CF6',
|
|
1344
|
+
description: 'Technical issues and bug reports',
|
|
1345
|
+
emailAddress: 'tech@example.com',
|
|
1346
|
+
slaFirstResponseHours: 1, slaResolutionHours: 8,
|
|
1347
|
+
})
|
|
1348
|
+
|
|
1349
|
+
const bugTag = await tagClient.create({ name: 'Bug', slug: 'bug', color: '#EF4444' })
|
|
1350
|
+
const featureTag = await tagClient.create({ name: 'Feature Request', slug: 'feature-request', color: '#F59E0B' })
|
|
1351
|
+
const urgentTag = await tagClient.create({ name: 'Urgent', slug: 'urgent', color: '#DC2626' })
|
|
1352
|
+
await tagClient.create({ name: 'Feedback', slug: 'feedback', color: '#06B6D4' })
|
|
1353
|
+
await tagClient.create({ name: 'Documentation', slug: 'documentation', color: '#6366F1' })
|
|
1354
|
+
|
|
1355
|
+
const now = Date.now()
|
|
1356
|
+
let counter = 0
|
|
1357
|
+
|
|
1358
|
+
async function createTicket(opts: {
|
|
1359
|
+
subject: string; department: string; status: string; priority: string;
|
|
1360
|
+
requesterEmail: string; requesterName: string; tags?: string[];
|
|
1361
|
+
messages: Array<{ body: string; senderType: string; senderEmail: string; senderName: string; visibility?: string; minutesAgo: number }>
|
|
1362
|
+
}) {
|
|
1363
|
+
counter++
|
|
1364
|
+
const lastMsg = opts.messages[opts.messages.length - 1]
|
|
1365
|
+
const ticket = await ticketAdmin.create({
|
|
1366
|
+
subject: opts.subject, status: opts.status, priority: opts.priority,
|
|
1367
|
+
department: opts.department, requesterEmail: opts.requesterEmail,
|
|
1368
|
+
requesterName: opts.requesterName,
|
|
1369
|
+
ticketNumber: \`TK-\${String(counter).padStart(6, '0')}\`,
|
|
1370
|
+
lastReplyAt: new Date(now - lastMsg.minutesAgo * 60_000),
|
|
1371
|
+
lastReplierType: lastMsg.senderType,
|
|
1372
|
+
tags: opts.tags ?? [],
|
|
1373
|
+
})
|
|
1374
|
+
for (const msg of opts.messages) {
|
|
1375
|
+
await messageAdmin.create({
|
|
1376
|
+
ticket: ticket.id, body: msg.body, bodyFormat: 'html',
|
|
1377
|
+
senderType: msg.senderType, senderEmail: msg.senderEmail,
|
|
1378
|
+
senderName: msg.senderName, visibility: msg.visibility ?? 'public',
|
|
1379
|
+
})
|
|
1380
|
+
}
|
|
1381
|
+
return ticket
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
await createTicket({
|
|
1385
|
+
subject: 'API returns 500 error on file upload',
|
|
1386
|
+
department: technical.id as string, status: 'open', priority: 'high',
|
|
1387
|
+
requesterEmail: 'john@acme.com', requesterName: 'John Smith',
|
|
1388
|
+
tags: [bugTag.id as string, urgentTag.id as string],
|
|
1389
|
+
messages: [
|
|
1390
|
+
{ body: '<p>When I upload files larger than 5MB, I get a 500 error. Started after the last update.</p>', senderType: 'customer', senderEmail: 'john@acme.com', senderName: 'John Smith', minutesAgo: 120 },
|
|
1391
|
+
{ body: '<p>Thanks for reporting this. I can see the error in our logs. Escalating to backend team.</p>', senderType: 'agent', senderEmail: 'sarah@support.com', senderName: 'Sarah Chen', minutesAgo: 90 },
|
|
1392
|
+
{ body: '<p>Any update? We have a deadline tomorrow.</p>', senderType: 'customer', senderEmail: 'john@acme.com', senderName: 'John Smith', minutesAgo: 30 },
|
|
1393
|
+
],
|
|
1394
|
+
})
|
|
1395
|
+
|
|
1396
|
+
await createTicket({
|
|
1397
|
+
subject: 'Invoice #2024-0847 has incorrect amount',
|
|
1398
|
+
department: billing.id as string, status: 'pending', priority: 'normal',
|
|
1399
|
+
requesterEmail: 'maria@startup.io', requesterName: 'Maria Garcia',
|
|
1400
|
+
messages: [
|
|
1401
|
+
{ body: '<p>Invoice #2024-0847 shows $299 instead of the agreed $199. Please correct.</p>', senderType: 'customer', senderEmail: 'maria@startup.io', senderName: 'Maria Garcia', minutesAgo: 1440 },
|
|
1402
|
+
{ body: '<p>Apologies for the discrepancy. Checking with billing team, should be resolved in 24h.</p>', senderType: 'agent', senderEmail: 'alex@support.com', senderName: 'Alex Johnson', minutesAgo: 1380 },
|
|
1403
|
+
],
|
|
1404
|
+
})
|
|
1405
|
+
|
|
1406
|
+
await createTicket({
|
|
1407
|
+
subject: 'Production site down — 503 errors',
|
|
1408
|
+
department: technical.id as string, status: 'open', priority: 'urgent',
|
|
1409
|
+
requesterEmail: 'ops@enterprise.co', requesterName: 'Emma Wilson',
|
|
1410
|
+
tags: [bugTag.id as string, urgentTag.id as string],
|
|
1411
|
+
messages: [
|
|
1412
|
+
{ body: '<p><strong>URGENT</strong> — Production returning 503 for all users. Need immediate help.</p>', senderType: 'customer', senderEmail: 'ops@enterprise.co', senderName: 'Emma Wilson', minutesAgo: 15 },
|
|
1413
|
+
],
|
|
1414
|
+
})
|
|
1415
|
+
|
|
1416
|
+
await createTicket({
|
|
1417
|
+
subject: 'Can we get dark mode for the dashboard?',
|
|
1418
|
+
department: support.id as string, status: 'resolved', priority: 'low',
|
|
1419
|
+
requesterEmail: 'dev@techcorp.com', requesterName: 'David Lee',
|
|
1420
|
+
tags: [featureTag.id as string],
|
|
1421
|
+
messages: [
|
|
1422
|
+
{ body: '<p>Would it be possible to add dark mode? Our team works late hours.</p>', senderType: 'customer', senderEmail: 'dev@techcorp.com', senderName: 'David Lee', minutesAgo: 10080 },
|
|
1423
|
+
{ body: '<p>Dark mode just shipped! Toggle it in Settings → Appearance.</p>', senderType: 'agent', senderEmail: 'sarah@support.com', senderName: 'Sarah Chen', minutesAgo: 4320 },
|
|
1424
|
+
{ body: '<p>Perfect, exactly what we needed. Thanks!</p>', senderType: 'customer', senderEmail: 'dev@techcorp.com', senderName: 'David Lee', minutesAgo: 4200 },
|
|
1425
|
+
],
|
|
1426
|
+
})
|
|
1427
|
+
|
|
1428
|
+
return { ok: true, created: { departments: 3, tags: 5, tickets: 4, messages: 9 } }
|
|
1429
|
+
}
|
|
1430
|
+
`),await n(a(e,`app/[locale]/(shell)/admin/seed/page.tsx`),`'use client'
|
|
1431
|
+
|
|
1432
|
+
import { Button } from '@murumets-ee/ui'
|
|
1433
|
+
import { useState } from 'react'
|
|
1434
|
+
import { seedContent, seedTicketing } from './actions'
|
|
1435
|
+
|
|
1436
|
+
export default function SeedPage() {
|
|
1437
|
+
const [result, setResult] = useState<string | null>(null)
|
|
1438
|
+
const [loading, setLoading] = useState(false)
|
|
1439
|
+
|
|
1440
|
+
async function run(action: () => Promise<unknown>) {
|
|
1441
|
+
setLoading(true)
|
|
1442
|
+
setResult(null)
|
|
1443
|
+
try {
|
|
1444
|
+
const res = await action()
|
|
1445
|
+
setResult(JSON.stringify(res, null, 2))
|
|
1446
|
+
} catch (e) {
|
|
1447
|
+
setResult(\`Error: \${e instanceof Error ? e.message : String(e)}\`)
|
|
1448
|
+
} finally {
|
|
1449
|
+
setLoading(false)
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
return (
|
|
1454
|
+
<div className="max-w-2xl mx-auto p-8">
|
|
1455
|
+
<h1 className="text-2xl font-bold mb-2">Seed Data</h1>
|
|
1456
|
+
<p className="text-sm text-zinc-500 dark:text-zinc-400 mb-6">
|
|
1457
|
+
Create demo content for testing. Only available in development mode.
|
|
1458
|
+
</p>
|
|
1459
|
+
|
|
1460
|
+
<div className="flex gap-3 flex-wrap">
|
|
1461
|
+
<Button onClick={() => run(seedContent)} loading={loading}>
|
|
1462
|
+
Seed Content
|
|
1463
|
+
</Button>
|
|
1464
|
+
<Button onClick={() => run(seedTicketing)} loading={loading} variant="secondary">
|
|
1465
|
+
Seed Ticketing
|
|
1466
|
+
</Button>
|
|
1467
|
+
</div>
|
|
1468
|
+
|
|
1469
|
+
{result && (
|
|
1470
|
+
<pre className="mt-6 bg-zinc-100 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg p-4 text-sm overflow-auto">
|
|
1471
|
+
{result}
|
|
1472
|
+
</pre>
|
|
1473
|
+
)}
|
|
1474
|
+
</div>
|
|
1475
|
+
)
|
|
1476
|
+
}
|
|
1252
1477
|
`),await n(a(e,`app/api/admin/[...path]/route.ts`),`import { createAdminApiHandler } from '@murumets-ee/admin-ui/server'
|
|
1253
1478
|
import { buildPermissionChecker, buildResourceCatalog } from '@murumets-ee/auth'
|
|
1254
1479
|
import { permissionRoutes } from '@murumets-ee/auth/admin'
|
|
@@ -1342,38 +1567,20 @@ const handler = createAdminApiHandler({
|
|
|
1342
1567
|
})
|
|
1343
1568
|
|
|
1344
1569
|
export const { GET, POST, PATCH, DELETE } = handler
|
|
1345
|
-
`)}async function c(e,t){await n(a(e,`lib/auth.ts`),`import {
|
|
1346
|
-
import {
|
|
1570
|
+
`)}async function c(e,t){await n(a(e,`lib/auth.ts`),`import { getAuth } from '@murumets-ee/auth'
|
|
1571
|
+
import { getToolkitApp } from './app'
|
|
1347
1572
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
}
|
|
1573
|
+
await getToolkitApp()
|
|
1574
|
+
|
|
1575
|
+
export const auth = getAuth()
|
|
1352
1576
|
`),await n(a(e,`lib/auth-client.ts`),`import { createClient } from '@murumets-ee/auth/client'
|
|
1353
1577
|
|
|
1354
1578
|
export const authClient = createClient()
|
|
1355
1579
|
`),await n(a(e,`app/api/auth/[...all]/route.ts`),`import { toNextJsHandler } from 'better-auth/next-js'
|
|
1356
|
-
import {
|
|
1357
|
-
|
|
1358
|
-
let _handler: ReturnType<typeof toNextJsHandler> | null = null
|
|
1359
|
-
|
|
1360
|
-
async function handler() {
|
|
1361
|
-
if (!_handler) {
|
|
1362
|
-
const auth = await getAuthInstance()
|
|
1363
|
-
_handler = toNextJsHandler(auth)
|
|
1364
|
-
}
|
|
1365
|
-
return _handler
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
export async function GET(req: Request) {
|
|
1369
|
-
const h = await handler()
|
|
1370
|
-
return h.GET(req)
|
|
1371
|
-
}
|
|
1580
|
+
import { auth } from '@/lib/auth'
|
|
1372
1581
|
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
return h.POST(req)
|
|
1376
|
-
}
|
|
1582
|
+
const { GET, POST } = toNextJsHandler(auth)
|
|
1583
|
+
export { GET, POST }
|
|
1377
1584
|
`),await n(a(e,`app/[locale]/auth/layout.tsx`),`import { AuthProviders } from './providers'
|
|
1378
1585
|
import type { ReactNode } from 'react'
|
|
1379
1586
|
|
|
@@ -1833,6 +2040,10 @@ export const routing = defineRouting({
|
|
|
1833
2040
|
locales: ['en'],
|
|
1834
2041
|
defaultLocale: 'en',
|
|
1835
2042
|
})
|
|
2043
|
+
`),await n(a(e,`i18n/navigation.ts`),`import { createNavigation } from 'next-intl/navigation'
|
|
2044
|
+
import { routing } from './routing'
|
|
2045
|
+
|
|
2046
|
+
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing)
|
|
1836
2047
|
`),await n(a(e,`i18n/request.ts`),`import { getRequestConfig } from 'next-intl/server'
|
|
1837
2048
|
import { hasLocale } from 'next-intl'
|
|
1838
2049
|
import { routing } from './routing'
|
|
@@ -2071,13 +2282,12 @@ export async function getToolkitApp(): Promise<ToolkitApp> {
|
|
|
2071
2282
|
}
|
|
2072
2283
|
return appInstance
|
|
2073
2284
|
}
|
|
2074
|
-
`),await n(a(i,`lib/auth.ts`),`import {
|
|
2075
|
-
import {
|
|
2285
|
+
`),await n(a(i,`lib/auth.ts`),`import { getAuth } from '@murumets-ee/auth'
|
|
2286
|
+
import { getToolkitApp } from './app'
|
|
2076
2287
|
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
}
|
|
2288
|
+
await getToolkitApp()
|
|
2289
|
+
|
|
2290
|
+
export const auth = getAuth()
|
|
2081
2291
|
`),await n(a(i,`lib/auth-client.ts`),`import { createClient } from '@murumets-ee/auth/client'
|
|
2082
2292
|
|
|
2083
2293
|
export const authClient = createClient()
|
|
@@ -2507,25 +2717,10 @@ export default async function HomePage() {
|
|
|
2507
2717
|
)
|
|
2508
2718
|
}
|
|
2509
2719
|
`),await n(a(l,`app/api/auth/[...all]/route.ts`),`import { toNextJsHandler } from 'better-auth/next-js'
|
|
2510
|
-
import {
|
|
2511
|
-
|
|
2512
|
-
let _handler: ReturnType<typeof toNextJsHandler> | null = null
|
|
2720
|
+
import { auth } from '@${c}/config/auth'
|
|
2513
2721
|
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
const auth = await getAuthInstance()
|
|
2517
|
-
_handler = toNextJsHandler(auth)
|
|
2518
|
-
}
|
|
2519
|
-
return _handler
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
export async function GET(req: Request) {
|
|
2523
|
-
return (await handler()).GET(req)
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2526
|
-
export async function POST(req: Request) {
|
|
2527
|
-
return (await handler()).POST(req)
|
|
2528
|
-
}
|
|
2722
|
+
const { GET, POST } = toNextJsHandler(auth)
|
|
2723
|
+
export { GET, POST }
|
|
2529
2724
|
`),await n(a(l,`app/[locale]/auth/layout.tsx`),`import { AuthProviders } from './providers'
|
|
2530
2725
|
import type { ReactNode } from 'react'
|
|
2531
2726
|
|