dineway 0.1.3 → 0.1.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.
- package/package.json +6 -3
- package/src/astro/routes/PluginRegistry.tsx +21 -0
- package/src/astro/routes/admin.astro +83 -0
- package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
- package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
- package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
- package/src/astro/routes/api/admin/bylines/index.ts +72 -0
- package/src/astro/routes/api/admin/comments/[id]/status.ts +120 -0
- package/src/astro/routes/api/admin/comments/[id].ts +64 -0
- package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
- package/src/astro/routes/api/admin/comments/counts.ts +30 -0
- package/src/astro/routes/api/admin/comments/index.ts +46 -0
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
- package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
- package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
- package/src/astro/routes/api/admin/plugins/index.ts +32 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +62 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +64 -0
- package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +62 -0
- package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
- package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
- package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
- package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
- package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
- package/src/astro/routes/api/admin/users/index.ts +66 -0
- package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
- package/src/astro/routes/api/auth/invite/accept.ts +52 -0
- package/src/astro/routes/api/auth/invite/complete.ts +86 -0
- package/src/astro/routes/api/auth/invite/index.ts +99 -0
- package/src/astro/routes/api/auth/logout.ts +40 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
- package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
- package/src/astro/routes/api/auth/me.ts +60 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +221 -0
- package/src/astro/routes/api/auth/oauth/[provider].ts +120 -0
- package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
- package/src/astro/routes/api/auth/passkey/index.ts +54 -0
- package/src/astro/routes/api/auth/passkey/options.ts +84 -0
- package/src/astro/routes/api/auth/passkey/register/options.ts +88 -0
- package/src/astro/routes/api/auth/passkey/register/verify.ts +119 -0
- package/src/astro/routes/api/auth/passkey/verify.ts +68 -0
- package/src/astro/routes/api/auth/signup/complete.ts +87 -0
- package/src/astro/routes/api/auth/signup/request.ts +77 -0
- package/src/astro/routes/api/auth/signup/verify.ts +53 -0
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +311 -0
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +105 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
- package/src/astro/routes/api/content/[collection]/index.ts +59 -0
- package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
- package/src/astro/routes/api/dashboard.ts +32 -0
- package/src/astro/routes/api/dev/emails.ts +36 -0
- package/src/astro/routes/api/import/probe.ts +47 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +531 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +296 -0
- package/src/astro/routes/api/import/wordpress/media.ts +338 -0
- package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
- package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +357 -0
- package/src/astro/routes/api/manifest.ts +63 -0
- package/src/astro/routes/api/mcp.ts +124 -0
- package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
- package/src/astro/routes/api/media/[id].ts +145 -0
- package/src/astro/routes/api/media/file/[...key].ts +79 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
- package/src/astro/routes/api/media/providers/index.ts +30 -0
- package/src/astro/routes/api/media/upload-url.ts +137 -0
- package/src/astro/routes/api/media.ts +202 -0
- package/src/astro/routes/api/menus/[name]/items.ts +87 -0
- package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
- package/src/astro/routes/api/menus/[name].ts +65 -0
- package/src/astro/routes/api/menus/index.ts +47 -0
- package/src/astro/routes/api/oauth/authorize.ts +417 -0
- package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
- package/src/astro/routes/api/oauth/device/code.ts +55 -0
- package/src/astro/routes/api/oauth/device/token.ts +69 -0
- package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
- package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
- package/src/astro/routes/api/oauth/token.ts +184 -0
- package/src/astro/routes/api/openapi.json.ts +32 -0
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
- package/src/astro/routes/api/redirects/404s/index.ts +72 -0
- package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
- package/src/astro/routes/api/redirects/[id].ts +84 -0
- package/src/astro/routes/api/redirects/index.ts +52 -0
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
- package/src/astro/routes/api/revisions/[revisionId]/restore.ts +62 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
- package/src/astro/routes/api/schema/collections/index.ts +47 -0
- package/src/astro/routes/api/schema/index.ts +109 -0
- package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
- package/src/astro/routes/api/schema/orphans/index.ts +26 -0
- package/src/astro/routes/api/search/enable.ts +64 -0
- package/src/astro/routes/api/search/index.ts +51 -0
- package/src/astro/routes/api/search/rebuild.ts +72 -0
- package/src/astro/routes/api/search/stats.ts +35 -0
- package/src/astro/routes/api/search/suggest.ts +49 -0
- package/src/astro/routes/api/sections/[slug].ts +84 -0
- package/src/astro/routes/api/sections/index.ts +52 -0
- package/src/astro/routes/api/settings/email.ts +150 -0
- package/src/astro/routes/api/settings.ts +67 -0
- package/src/astro/routes/api/setup/admin-verify.ts +102 -0
- package/src/astro/routes/api/setup/admin.ts +96 -0
- package/src/astro/routes/api/setup/dev-bypass.ts +200 -0
- package/src/astro/routes/api/setup/dev-reset.ts +40 -0
- package/src/astro/routes/api/setup/index.ts +127 -0
- package/src/astro/routes/api/setup/status.ts +122 -0
- package/src/astro/routes/api/snapshot.ts +76 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
- package/src/astro/routes/api/taxonomies/index.ts +59 -0
- package/src/astro/routes/api/themes/preview.ts +78 -0
- package/src/astro/routes/api/typegen.ts +114 -0
- package/src/astro/routes/api/well-known/auth.ts +69 -0
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +45 -0
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +38 -0
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +72 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
- package/src/astro/routes/api/widget-areas/[name].ts +87 -0
- package/src/astro/routes/api/widget-areas/index.ts +99 -0
- package/src/astro/routes/api/widget-components.ts +22 -0
- package/src/astro/routes/robots.txt.ts +81 -0
- package/src/astro/routes/sitemap-[collection].xml.ts +104 -0
- package/src/astro/routes/sitemap.xml.ts +92 -0
- package/src/components/Break.astro +45 -0
- package/src/components/Button.astro +71 -0
- package/src/components/Buttons.astro +49 -0
- package/src/components/Code.astro +59 -0
- package/src/components/Columns.astro +59 -0
- package/src/components/CommentForm.astro +315 -0
- package/src/components/Comments.astro +232 -0
- package/src/components/Cover.astro +128 -0
- package/src/components/DinewayBodyEnd.astro +32 -0
- package/src/components/DinewayBodyStart.astro +32 -0
- package/src/components/DinewayHead.astro +53 -0
- package/src/components/DinewayImage.astro +178 -0
- package/src/components/DinewayMedia.astro +167 -0
- package/src/components/Embed.astro +128 -0
- package/src/components/File.astro +122 -0
- package/src/components/Gallery.astro +93 -0
- package/src/components/HtmlBlock.astro +33 -0
- package/src/components/Image.astro +178 -0
- package/src/components/InlineEditor.astro +27 -0
- package/src/components/InlinePortableTextEditor.tsx +1937 -0
- package/src/components/LiveSearch.astro +614 -0
- package/src/components/PortableText.astro +51 -0
- package/src/components/Pullquote.astro +51 -0
- package/src/components/Table.astro +108 -0
- package/src/components/WidgetArea.astro +22 -0
- package/src/components/WidgetRenderer.astro +72 -0
- package/src/components/index.ts +116 -0
- package/src/components/marks/Link.astro +31 -0
- package/src/components/marks/StrikeThrough.astro +7 -0
- package/src/components/marks/Subscript.astro +7 -0
- package/src/components/marks/Superscript.astro +7 -0
- package/src/components/marks/Underline.astro +7 -0
- package/src/components/widgets/Archives.astro +65 -0
- package/src/components/widgets/Categories.astro +35 -0
- package/src/components/widgets/RecentPosts.astro +51 -0
- package/src/components/widgets/Search.astro +18 -0
- package/src/components/widgets/Tags.astro +38 -0
- package/src/ui.ts +75 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dineway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Agentic Web builder with WordPress migration support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
-
"locals.d.ts"
|
|
13
|
+
"locals.d.ts",
|
|
14
|
+
"src/astro/routes",
|
|
15
|
+
"src/ui.ts",
|
|
16
|
+
"src/components"
|
|
14
17
|
],
|
|
15
18
|
"exports": {
|
|
16
19
|
".": {
|
|
@@ -259,8 +262,8 @@
|
|
|
259
262
|
"ulidx": "^2.4.1",
|
|
260
263
|
"upng-js": "^2.1.0",
|
|
261
264
|
"zod": "^4.3.5",
|
|
262
|
-
"@dineway-ai/admin": "0.1.3",
|
|
263
265
|
"@dineway-ai/auth": "0.1.3",
|
|
266
|
+
"@dineway-ai/admin": "0.1.3",
|
|
264
267
|
"@dineway-ai/gutenberg-to-portable-text": "0.1.3"
|
|
265
268
|
},
|
|
266
269
|
"optionalDependencies": {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Imports plugin admin modules from the virtual module and passes them
|
|
5
|
+
* to AdminApp via props. This ensures plugin components are bundled
|
|
6
|
+
* together with the admin app and available via React context.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AdminApp } from "@dineway-ai/admin";
|
|
10
|
+
import type { Messages } from "@lingui/core";
|
|
11
|
+
// @ts-ignore - virtual module generated by integration
|
|
12
|
+
import { pluginAdmins } from "virtual:dineway/admin-registry";
|
|
13
|
+
|
|
14
|
+
interface AdminWrapperProps {
|
|
15
|
+
locale: string;
|
|
16
|
+
messages: Messages;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function AdminWrapper({ locale, messages }: AdminWrapperProps) {
|
|
20
|
+
return <AdminApp pluginAdmins={pluginAdmins} locale={locale} messages={messages} />;
|
|
21
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Admin shell route - injected by Dineway integration
|
|
4
|
+
*
|
|
5
|
+
* This page serves the Dineway admin React SPA.
|
|
6
|
+
* AdminWrapper imports plugin admin modules and passes them to AdminApp.
|
|
7
|
+
*/
|
|
8
|
+
import "@dineway-ai/admin/styles.css";
|
|
9
|
+
// Use package-qualified import so Astro generates a proper module URL
|
|
10
|
+
// (relative imports resolve to absolute paths which break client hydration)
|
|
11
|
+
import AdminWrapper from "dineway/routes/PluginRegistry";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
import { resolveLocale } from "@dineway-ai/admin/locales";
|
|
16
|
+
|
|
17
|
+
const resolvedLocale = resolveLocale(Astro.request);
|
|
18
|
+
const { messages } = await import(`@dineway-ai/admin/locales/${resolvedLocale}/messages.mjs`);
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<!doctype html>
|
|
22
|
+
<html lang={resolvedLocale}>
|
|
23
|
+
<head>
|
|
24
|
+
<meta charset="UTF-8" />
|
|
25
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
26
|
+
<link rel="icon" href="/favicon.svg" />
|
|
27
|
+
<title>Dineway Admin</title>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
<div id="admin-root" class="min-h-screen">
|
|
31
|
+
<div id="dineway-boot-loader">
|
|
32
|
+
<style>
|
|
33
|
+
#dineway-boot-loader {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
min-height: 100vh;
|
|
38
|
+
color-scheme: light dark;
|
|
39
|
+
background: light-dark(hsl(0 0% 100%), hsl(222.2 84% 4.9%));
|
|
40
|
+
}
|
|
41
|
+
#dineway-boot-loader .loader-inner {
|
|
42
|
+
text-align: center;
|
|
43
|
+
}
|
|
44
|
+
#dineway-boot-loader .spinner {
|
|
45
|
+
width: 24px;
|
|
46
|
+
height: 24px;
|
|
47
|
+
margin: 0 auto;
|
|
48
|
+
border: 2.5px solid
|
|
49
|
+
light-dark(
|
|
50
|
+
hsl(215.4 16.3% 46.9% / 0.3),
|
|
51
|
+
hsl(215 20.2% 65.1% / 0.3)
|
|
52
|
+
);
|
|
53
|
+
border-top-color: light-dark(
|
|
54
|
+
hsl(215.4 16.3% 46.9%),
|
|
55
|
+
hsl(215 20.2% 65.1%)
|
|
56
|
+
);
|
|
57
|
+
border-radius: 50%;
|
|
58
|
+
animation: dineway-spin 0.8s linear infinite;
|
|
59
|
+
}
|
|
60
|
+
#dineway-boot-loader p {
|
|
61
|
+
margin-top: 1rem;
|
|
62
|
+
font-family:
|
|
63
|
+
system-ui,
|
|
64
|
+
-apple-system,
|
|
65
|
+
sans-serif;
|
|
66
|
+
font-size: 0.875rem;
|
|
67
|
+
color: light-dark(hsl(215.4 16.3% 46.9%), hsl(215 20.2% 65.1%));
|
|
68
|
+
}
|
|
69
|
+
@keyframes dineway-spin {
|
|
70
|
+
to {
|
|
71
|
+
transform: rotate(360deg);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
</style>
|
|
75
|
+
<div class="loader-inner">
|
|
76
|
+
<div class="spinner"></div>
|
|
77
|
+
<p>Loading Dineway...</p>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<AdminWrapper client:only="react" locale={resolvedLocale} messages={messages} />
|
|
81
|
+
</div>
|
|
82
|
+
</body>
|
|
83
|
+
</html>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PATCH/DELETE /_dineway/api/admin/allowed-domains/[domain]
|
|
3
|
+
*
|
|
4
|
+
* Admin endpoints for managing a specific allowed domain.
|
|
5
|
+
* PATCH - Update domain settings (enabled, defaultRole)
|
|
6
|
+
* DELETE - Remove an allowed domain
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
|
|
11
|
+
export const prerender = false;
|
|
12
|
+
|
|
13
|
+
import { Role, roleFromLevel } from "@dineway-ai/auth";
|
|
14
|
+
import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
|
|
15
|
+
|
|
16
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
17
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
18
|
+
import { allowedDomainUpdateBody } from "#api/schemas.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* PATCH - Update domain settings
|
|
22
|
+
*/
|
|
23
|
+
export const PATCH: APIRoute = async ({ params, request, locals }) => {
|
|
24
|
+
const { dineway, user } = locals;
|
|
25
|
+
const { domain } = params;
|
|
26
|
+
|
|
27
|
+
if (!dineway?.db) {
|
|
28
|
+
return apiError("NOT_CONFIGURED", "Database not configured", 500);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!domain) {
|
|
32
|
+
return apiError("VALIDATION_ERROR", "Domain is required", 400);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!user || user.role < Role.ADMIN) {
|
|
36
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const body = await parseBody(request, allowedDomainUpdateBody);
|
|
43
|
+
if (isParseError(body)) return body;
|
|
44
|
+
|
|
45
|
+
// Check if domain exists
|
|
46
|
+
const existing = await adapter.getAllowedDomain(domain);
|
|
47
|
+
if (!existing) {
|
|
48
|
+
return apiError("NOT_FOUND", "Domain not found", 404);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Role is already validated as RoleLevel by Zod schema
|
|
52
|
+
const defaultRole = body.defaultRole;
|
|
53
|
+
|
|
54
|
+
// Update domain
|
|
55
|
+
const enabled = body.enabled ?? existing.enabled;
|
|
56
|
+
await adapter.updateAllowedDomain(domain, enabled, defaultRole);
|
|
57
|
+
|
|
58
|
+
// Fetch updated domain
|
|
59
|
+
const updated = await adapter.getAllowedDomain(domain);
|
|
60
|
+
|
|
61
|
+
return apiSuccess({
|
|
62
|
+
success: true,
|
|
63
|
+
domain: updated
|
|
64
|
+
? {
|
|
65
|
+
domain: updated.domain,
|
|
66
|
+
defaultRole: updated.defaultRole,
|
|
67
|
+
roleName: roleFromLevel(updated.defaultRole),
|
|
68
|
+
enabled: updated.enabled,
|
|
69
|
+
createdAt: updated.createdAt.toISOString(),
|
|
70
|
+
}
|
|
71
|
+
: null,
|
|
72
|
+
});
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return handleError(error, "Failed to update allowed domain", "DOMAIN_UPDATE_ERROR");
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* DELETE - Remove an allowed domain
|
|
80
|
+
*/
|
|
81
|
+
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
82
|
+
const { dineway, user } = locals;
|
|
83
|
+
const { domain } = params;
|
|
84
|
+
|
|
85
|
+
if (!dineway?.db) {
|
|
86
|
+
return apiError("NOT_CONFIGURED", "Database not configured", 500);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!domain) {
|
|
90
|
+
return apiError("VALIDATION_ERROR", "Domain is required", 400);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!user || user.role < Role.ADMIN) {
|
|
94
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// Check if domain exists (optional - delete is idempotent)
|
|
101
|
+
const existing = await adapter.getAllowedDomain(domain);
|
|
102
|
+
if (!existing) {
|
|
103
|
+
return apiError("NOT_FOUND", "Domain not found", 404);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await adapter.deleteAllowedDomain(domain);
|
|
107
|
+
|
|
108
|
+
return apiSuccess({ success: true });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return handleError(error, "Failed to delete allowed domain", "DOMAIN_DELETE_ERROR");
|
|
111
|
+
}
|
|
112
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET/POST /_dineway/api/admin/allowed-domains
|
|
3
|
+
*
|
|
4
|
+
* Admin endpoints for managing allowed signup domains.
|
|
5
|
+
* GET - List all allowed domains
|
|
6
|
+
* POST - Add a new allowed domain
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
|
|
11
|
+
export const prerender = false;
|
|
12
|
+
|
|
13
|
+
import { Role, roleFromLevel } from "@dineway-ai/auth";
|
|
14
|
+
import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
|
|
15
|
+
|
|
16
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
17
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
18
|
+
import { allowedDomainCreateBody } from "#api/schemas.js";
|
|
19
|
+
|
|
20
|
+
const DOMAIN_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9-]*(\.[a-zA-Z0-9-]+)+$/;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* GET - List all allowed domains
|
|
24
|
+
*/
|
|
25
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
26
|
+
const { dineway, user } = locals;
|
|
27
|
+
|
|
28
|
+
if (!dineway?.db) {
|
|
29
|
+
return apiError("NOT_CONFIGURED", "Database not configured", 500);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!user || user.role < Role.ADMIN) {
|
|
33
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const domains = await adapter.getAllowedDomains();
|
|
40
|
+
|
|
41
|
+
return apiSuccess({
|
|
42
|
+
domains: domains.map((d) => ({
|
|
43
|
+
domain: d.domain,
|
|
44
|
+
defaultRole: d.defaultRole,
|
|
45
|
+
roleName: roleFromLevel(d.defaultRole),
|
|
46
|
+
enabled: d.enabled,
|
|
47
|
+
createdAt: d.createdAt.toISOString(),
|
|
48
|
+
})),
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return handleError(error, "Failed to list allowed domains", "DOMAIN_LIST_ERROR");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* POST - Add a new allowed domain
|
|
57
|
+
*/
|
|
58
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
59
|
+
const { dineway, user } = locals;
|
|
60
|
+
|
|
61
|
+
if (!dineway?.db) {
|
|
62
|
+
return apiError("NOT_CONFIGURED", "Database not configured", 500);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!user || user.role < Role.ADMIN) {
|
|
66
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const adapter = createKyselyAdapter(dineway.db);
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const body = await parseBody(request, allowedDomainCreateBody);
|
|
73
|
+
if (isParseError(body)) return body;
|
|
74
|
+
|
|
75
|
+
// Role is already validated as RoleLevel by Zod schema
|
|
76
|
+
const defaultRole = body.defaultRole;
|
|
77
|
+
|
|
78
|
+
// Validate domain format (no protocol, just domain)
|
|
79
|
+
const cleanDomain = body.domain.toLowerCase().trim();
|
|
80
|
+
if (!DOMAIN_REGEX.test(cleanDomain)) {
|
|
81
|
+
return apiError("VALIDATION_ERROR", "Invalid domain format", 400);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check if domain already exists
|
|
85
|
+
const existing = await adapter.getAllowedDomain(cleanDomain);
|
|
86
|
+
if (existing) {
|
|
87
|
+
return apiError("CONFLICT", "Domain already exists", 409);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const domain = await adapter.createAllowedDomain(cleanDomain, defaultRole);
|
|
91
|
+
|
|
92
|
+
return apiSuccess(
|
|
93
|
+
{
|
|
94
|
+
success: true,
|
|
95
|
+
domain: {
|
|
96
|
+
domain: domain.domain,
|
|
97
|
+
defaultRole: domain.defaultRole,
|
|
98
|
+
roleName: roleFromLevel(domain.defaultRole),
|
|
99
|
+
enabled: domain.enabled,
|
|
100
|
+
createdAt: domain.createdAt.toISOString(),
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
201,
|
|
104
|
+
);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return handleError(error, "Failed to create allowed domain", "DOMAIN_CREATE_ERROR");
|
|
107
|
+
}
|
|
108
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single API token endpoint
|
|
3
|
+
*
|
|
4
|
+
* DELETE /_dineway/api/admin/api-tokens/:id — Revoke a token
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Role } from "@dineway-ai/auth";
|
|
8
|
+
import type { APIRoute } from "astro";
|
|
9
|
+
|
|
10
|
+
import { apiError, handleError, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleApiTokenRevoke } from "#api/handlers/api-tokens.js";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Revoke (delete) an API token.
|
|
17
|
+
*/
|
|
18
|
+
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
19
|
+
const { dineway, user } = locals;
|
|
20
|
+
|
|
21
|
+
if (!dineway?.db) {
|
|
22
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!user || user.role < Role.ADMIN) {
|
|
26
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const tokenId = params.id;
|
|
30
|
+
if (!tokenId) {
|
|
31
|
+
return apiError("VALIDATION_ERROR", "Token ID is required", 400);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const result = await handleApiTokenRevoke(dineway.db, tokenId, user.id);
|
|
36
|
+
return unwrapResult(result);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return handleError(error, "Failed to revoke API token", "TOKEN_REVOKE_ERROR");
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API token management endpoints
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/api-tokens — List tokens for current user
|
|
5
|
+
* POST /_dineway/api/admin/api-tokens — Create a new token
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Role } from "@dineway-ai/auth";
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
import { apiError, handleError, unwrapResult } from "#api/error.js";
|
|
13
|
+
import { handleApiTokenCreate, handleApiTokenList } from "#api/handlers/api-tokens.js";
|
|
14
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
15
|
+
import { VALID_SCOPES } from "#auth/api-tokens.js";
|
|
16
|
+
|
|
17
|
+
export const prerender = false;
|
|
18
|
+
|
|
19
|
+
const createTokenSchema = z.object({
|
|
20
|
+
name: z.string().min(1).max(100),
|
|
21
|
+
scopes: z.array(z.enum(VALID_SCOPES)).min(1),
|
|
22
|
+
expiresAt: z.string().datetime().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* List API tokens for the current user.
|
|
27
|
+
* Admins can list all tokens (future: add ?userId= filter).
|
|
28
|
+
*/
|
|
29
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
30
|
+
const { dineway, user } = locals;
|
|
31
|
+
|
|
32
|
+
if (!dineway?.db) {
|
|
33
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!user || user.role < Role.ADMIN) {
|
|
37
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = await handleApiTokenList(dineway.db, user.id);
|
|
41
|
+
return unwrapResult(result);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create a new API token.
|
|
46
|
+
* Returns the raw token once — it cannot be retrieved again.
|
|
47
|
+
*/
|
|
48
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
49
|
+
const { dineway, user } = locals;
|
|
50
|
+
|
|
51
|
+
if (!dineway?.db) {
|
|
52
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!user || user.role < Role.ADMIN) {
|
|
56
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const body = await parseBody(request, createTokenSchema);
|
|
61
|
+
if (isParseError(body)) return body;
|
|
62
|
+
|
|
63
|
+
const result = await handleApiTokenCreate(dineway.db, user.id, body);
|
|
64
|
+
return unwrapResult(result, 201);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return handleError(error, "Failed to create API token", "TOKEN_CREATE_ERROR");
|
|
67
|
+
}
|
|
68
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Role } from "@dineway-ai/auth";
|
|
2
|
+
import type { APIRoute } from "astro";
|
|
3
|
+
|
|
4
|
+
import { requirePerm } from "#api/authorize.js";
|
|
5
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
6
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
7
|
+
import { bylineUpdateBody } from "#api/schemas.js";
|
|
8
|
+
import { BylineRepository } from "#db/repositories/byline.js";
|
|
9
|
+
|
|
10
|
+
export const prerender = false;
|
|
11
|
+
|
|
12
|
+
function requireEditor(user: { role: number } | undefined): Response | null {
|
|
13
|
+
if (!user || user.role < Role.EDITOR) {
|
|
14
|
+
return apiError("FORBIDDEN", "Editor privileges required", 403);
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
20
|
+
const { dineway, user } = locals;
|
|
21
|
+
// Read access uses content:read so all authenticated roles can view byline data
|
|
22
|
+
const denied = requirePerm(user, "content:read");
|
|
23
|
+
if (denied) return denied;
|
|
24
|
+
|
|
25
|
+
if (!dineway?.db) {
|
|
26
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const repo = new BylineRepository(dineway.db);
|
|
31
|
+
const byline = await repo.findById(params.id!);
|
|
32
|
+
if (!byline) return apiError("NOT_FOUND", "Byline not found", 404);
|
|
33
|
+
return apiSuccess(byline);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return handleError(error, "Failed to get byline", "BYLINE_GET_ERROR");
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
40
|
+
const { dineway, user } = locals;
|
|
41
|
+
const denied = requireEditor(user);
|
|
42
|
+
if (denied) return denied;
|
|
43
|
+
|
|
44
|
+
if (!dineway?.db) {
|
|
45
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const body = await parseBody(request, bylineUpdateBody);
|
|
49
|
+
if (isParseError(body)) return body;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const repo = new BylineRepository(dineway.db);
|
|
53
|
+
const byline = await repo.update(params.id!, {
|
|
54
|
+
slug: body.slug,
|
|
55
|
+
displayName: body.displayName,
|
|
56
|
+
bio: body.bio ?? null,
|
|
57
|
+
avatarMediaId: body.avatarMediaId ?? null,
|
|
58
|
+
websiteUrl: body.websiteUrl ?? null,
|
|
59
|
+
userId: body.userId ?? null,
|
|
60
|
+
isGuest: body.isGuest,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!byline) return apiError("NOT_FOUND", "Byline not found", 404);
|
|
64
|
+
return apiSuccess(byline);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return handleError(error, "Failed to update byline", "BYLINE_UPDATE_ERROR");
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
71
|
+
const { dineway, user } = locals;
|
|
72
|
+
const denied = requireEditor(user);
|
|
73
|
+
if (denied) return denied;
|
|
74
|
+
|
|
75
|
+
if (!dineway?.db) {
|
|
76
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const repo = new BylineRepository(dineway.db);
|
|
81
|
+
const deleted = await repo.delete(params.id!);
|
|
82
|
+
if (!deleted) return apiError("NOT_FOUND", "Byline not found", 404);
|
|
83
|
+
return apiSuccess({ deleted: true });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return handleError(error, "Failed to delete byline", "BYLINE_DELETE_ERROR");
|
|
86
|
+
}
|
|
87
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Role } from "@dineway-ai/auth";
|
|
2
|
+
import type { APIRoute } from "astro";
|
|
3
|
+
|
|
4
|
+
import { requirePerm } from "#api/authorize.js";
|
|
5
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
6
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
7
|
+
import { bylineCreateBody, bylinesListQuery } from "#api/schemas.js";
|
|
8
|
+
import { BylineRepository } from "#db/repositories/byline.js";
|
|
9
|
+
|
|
10
|
+
export const prerender = false;
|
|
11
|
+
|
|
12
|
+
export const GET: APIRoute = async ({ url, locals }) => {
|
|
13
|
+
const { dineway, user } = locals;
|
|
14
|
+
|
|
15
|
+
if (!dineway?.db) {
|
|
16
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Read access uses content:read so all authenticated roles can view byline data
|
|
20
|
+
const denied = requirePerm(user, "content:read");
|
|
21
|
+
if (denied) return denied;
|
|
22
|
+
|
|
23
|
+
const query = parseQuery(url, bylinesListQuery);
|
|
24
|
+
if (isParseError(query)) return query;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const repo = new BylineRepository(dineway.db);
|
|
28
|
+
const result = await repo.findMany({
|
|
29
|
+
search: query.search,
|
|
30
|
+
isGuest: query.isGuest,
|
|
31
|
+
userId: query.userId,
|
|
32
|
+
cursor: query.cursor,
|
|
33
|
+
limit: query.limit,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return apiSuccess(result);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return handleError(error, "Failed to list bylines", "BYLINE_LIST_ERROR");
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
43
|
+
const { dineway, user } = locals;
|
|
44
|
+
|
|
45
|
+
if (!dineway?.db) {
|
|
46
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!user || user.role < Role.EDITOR) {
|
|
50
|
+
return apiError("FORBIDDEN", "Editor privileges required", 403);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const body = await parseBody(request, bylineCreateBody);
|
|
54
|
+
if (isParseError(body)) return body;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const repo = new BylineRepository(dineway.db);
|
|
58
|
+
const byline = await repo.create({
|
|
59
|
+
slug: body.slug,
|
|
60
|
+
displayName: body.displayName,
|
|
61
|
+
bio: body.bio ?? null,
|
|
62
|
+
avatarMediaId: body.avatarMediaId ?? null,
|
|
63
|
+
websiteUrl: body.websiteUrl ?? null,
|
|
64
|
+
userId: body.userId ?? null,
|
|
65
|
+
isGuest: body.isGuest,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return apiSuccess(byline, 201);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return handleError(error, "Failed to create byline", "BYLINE_CREATE_ERROR");
|
|
71
|
+
}
|
|
72
|
+
};
|