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.
Files changed (191) hide show
  1. package/package.json +6 -3
  2. package/src/astro/routes/PluginRegistry.tsx +21 -0
  3. package/src/astro/routes/admin.astro +83 -0
  4. package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
  5. package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
  6. package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
  7. package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
  8. package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
  9. package/src/astro/routes/api/admin/bylines/index.ts +72 -0
  10. package/src/astro/routes/api/admin/comments/[id]/status.ts +120 -0
  11. package/src/astro/routes/api/admin/comments/[id].ts +64 -0
  12. package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
  13. package/src/astro/routes/api/admin/comments/counts.ts +30 -0
  14. package/src/astro/routes/api/admin/comments/index.ts +46 -0
  15. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
  16. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
  17. package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
  18. package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
  19. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
  20. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
  21. package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
  22. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
  23. package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
  24. package/src/astro/routes/api/admin/plugins/index.ts +32 -0
  25. package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +62 -0
  26. package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
  27. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +64 -0
  28. package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
  29. package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
  30. package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
  31. package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +62 -0
  32. package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
  33. package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
  34. package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
  35. package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
  36. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
  37. package/src/astro/routes/api/admin/users/index.ts +66 -0
  38. package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
  39. package/src/astro/routes/api/auth/invite/accept.ts +52 -0
  40. package/src/astro/routes/api/auth/invite/complete.ts +86 -0
  41. package/src/astro/routes/api/auth/invite/index.ts +99 -0
  42. package/src/astro/routes/api/auth/logout.ts +40 -0
  43. package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
  44. package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
  45. package/src/astro/routes/api/auth/me.ts +60 -0
  46. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +221 -0
  47. package/src/astro/routes/api/auth/oauth/[provider].ts +120 -0
  48. package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
  49. package/src/astro/routes/api/auth/passkey/index.ts +54 -0
  50. package/src/astro/routes/api/auth/passkey/options.ts +84 -0
  51. package/src/astro/routes/api/auth/passkey/register/options.ts +88 -0
  52. package/src/astro/routes/api/auth/passkey/register/verify.ts +119 -0
  53. package/src/astro/routes/api/auth/passkey/verify.ts +68 -0
  54. package/src/astro/routes/api/auth/signup/complete.ts +87 -0
  55. package/src/astro/routes/api/auth/signup/request.ts +77 -0
  56. package/src/astro/routes/api/auth/signup/verify.ts +53 -0
  57. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +311 -0
  58. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
  59. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
  60. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
  61. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
  62. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
  63. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
  64. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
  65. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
  66. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +105 -0
  67. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
  68. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
  69. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
  70. package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
  71. package/src/astro/routes/api/content/[collection]/index.ts +59 -0
  72. package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
  73. package/src/astro/routes/api/dashboard.ts +32 -0
  74. package/src/astro/routes/api/dev/emails.ts +36 -0
  75. package/src/astro/routes/api/import/probe.ts +47 -0
  76. package/src/astro/routes/api/import/wordpress/analyze.ts +531 -0
  77. package/src/astro/routes/api/import/wordpress/execute.ts +296 -0
  78. package/src/astro/routes/api/import/wordpress/media.ts +338 -0
  79. package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
  80. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
  81. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
  82. package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
  83. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +357 -0
  84. package/src/astro/routes/api/manifest.ts +63 -0
  85. package/src/astro/routes/api/mcp.ts +124 -0
  86. package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
  87. package/src/astro/routes/api/media/[id].ts +145 -0
  88. package/src/astro/routes/api/media/file/[...key].ts +79 -0
  89. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
  90. package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
  91. package/src/astro/routes/api/media/providers/index.ts +30 -0
  92. package/src/astro/routes/api/media/upload-url.ts +137 -0
  93. package/src/astro/routes/api/media.ts +202 -0
  94. package/src/astro/routes/api/menus/[name]/items.ts +87 -0
  95. package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
  96. package/src/astro/routes/api/menus/[name].ts +65 -0
  97. package/src/astro/routes/api/menus/index.ts +47 -0
  98. package/src/astro/routes/api/oauth/authorize.ts +417 -0
  99. package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
  100. package/src/astro/routes/api/oauth/device/code.ts +55 -0
  101. package/src/astro/routes/api/oauth/device/token.ts +69 -0
  102. package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
  103. package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
  104. package/src/astro/routes/api/oauth/token.ts +184 -0
  105. package/src/astro/routes/api/openapi.json.ts +32 -0
  106. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
  107. package/src/astro/routes/api/redirects/404s/index.ts +72 -0
  108. package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
  109. package/src/astro/routes/api/redirects/[id].ts +84 -0
  110. package/src/astro/routes/api/redirects/index.ts +52 -0
  111. package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
  112. package/src/astro/routes/api/revisions/[revisionId]/restore.ts +62 -0
  113. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
  114. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
  115. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
  116. package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
  117. package/src/astro/routes/api/schema/collections/index.ts +47 -0
  118. package/src/astro/routes/api/schema/index.ts +109 -0
  119. package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
  120. package/src/astro/routes/api/schema/orphans/index.ts +26 -0
  121. package/src/astro/routes/api/search/enable.ts +64 -0
  122. package/src/astro/routes/api/search/index.ts +51 -0
  123. package/src/astro/routes/api/search/rebuild.ts +72 -0
  124. package/src/astro/routes/api/search/stats.ts +35 -0
  125. package/src/astro/routes/api/search/suggest.ts +49 -0
  126. package/src/astro/routes/api/sections/[slug].ts +84 -0
  127. package/src/astro/routes/api/sections/index.ts +52 -0
  128. package/src/astro/routes/api/settings/email.ts +150 -0
  129. package/src/astro/routes/api/settings.ts +67 -0
  130. package/src/astro/routes/api/setup/admin-verify.ts +102 -0
  131. package/src/astro/routes/api/setup/admin.ts +96 -0
  132. package/src/astro/routes/api/setup/dev-bypass.ts +200 -0
  133. package/src/astro/routes/api/setup/dev-reset.ts +40 -0
  134. package/src/astro/routes/api/setup/index.ts +127 -0
  135. package/src/astro/routes/api/setup/status.ts +122 -0
  136. package/src/astro/routes/api/snapshot.ts +76 -0
  137. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
  138. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
  139. package/src/astro/routes/api/taxonomies/index.ts +59 -0
  140. package/src/astro/routes/api/themes/preview.ts +78 -0
  141. package/src/astro/routes/api/typegen.ts +114 -0
  142. package/src/astro/routes/api/well-known/auth.ts +69 -0
  143. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +45 -0
  144. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +38 -0
  145. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +72 -0
  146. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
  147. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
  148. package/src/astro/routes/api/widget-areas/[name].ts +87 -0
  149. package/src/astro/routes/api/widget-areas/index.ts +99 -0
  150. package/src/astro/routes/api/widget-components.ts +22 -0
  151. package/src/astro/routes/robots.txt.ts +81 -0
  152. package/src/astro/routes/sitemap-[collection].xml.ts +104 -0
  153. package/src/astro/routes/sitemap.xml.ts +92 -0
  154. package/src/components/Break.astro +45 -0
  155. package/src/components/Button.astro +71 -0
  156. package/src/components/Buttons.astro +49 -0
  157. package/src/components/Code.astro +59 -0
  158. package/src/components/Columns.astro +59 -0
  159. package/src/components/CommentForm.astro +315 -0
  160. package/src/components/Comments.astro +232 -0
  161. package/src/components/Cover.astro +128 -0
  162. package/src/components/DinewayBodyEnd.astro +32 -0
  163. package/src/components/DinewayBodyStart.astro +32 -0
  164. package/src/components/DinewayHead.astro +53 -0
  165. package/src/components/DinewayImage.astro +178 -0
  166. package/src/components/DinewayMedia.astro +167 -0
  167. package/src/components/Embed.astro +128 -0
  168. package/src/components/File.astro +122 -0
  169. package/src/components/Gallery.astro +93 -0
  170. package/src/components/HtmlBlock.astro +33 -0
  171. package/src/components/Image.astro +178 -0
  172. package/src/components/InlineEditor.astro +27 -0
  173. package/src/components/InlinePortableTextEditor.tsx +1937 -0
  174. package/src/components/LiveSearch.astro +614 -0
  175. package/src/components/PortableText.astro +51 -0
  176. package/src/components/Pullquote.astro +51 -0
  177. package/src/components/Table.astro +108 -0
  178. package/src/components/WidgetArea.astro +22 -0
  179. package/src/components/WidgetRenderer.astro +72 -0
  180. package/src/components/index.ts +116 -0
  181. package/src/components/marks/Link.astro +31 -0
  182. package/src/components/marks/StrikeThrough.astro +7 -0
  183. package/src/components/marks/Subscript.astro +7 -0
  184. package/src/components/marks/Superscript.astro +7 -0
  185. package/src/components/marks/Underline.astro +7 -0
  186. package/src/components/widgets/Archives.astro +65 -0
  187. package/src/components/widgets/Categories.astro +35 -0
  188. package/src/components/widgets/RecentPosts.astro +51 -0
  189. package/src/components/widgets/Search.astro +18 -0
  190. package/src/components/widgets/Tags.astro +38 -0
  191. package/src/ui.ts +75 -0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Taxonomy terms list and create endpoint
3
+ *
4
+ * GET /_dineway/api/taxonomies/:name/terms - List all terms (tree for hierarchical)
5
+ * POST /_dineway/api/taxonomies/:name/terms - Create a new term
6
+ */
7
+
8
+ import type { APIRoute } from "astro";
9
+
10
+ import { requirePerm } from "#api/authorize.js";
11
+ import { apiError, handleError, requireDb, unwrapResult } from "#api/error.js";
12
+ import { handleTermCreate, handleTermList } from "#api/handlers/taxonomies.js";
13
+ import { isParseError, parseBody } from "#api/parse.js";
14
+ import { createTermBody } from "#api/schemas.js";
15
+
16
+ export const prerender = false;
17
+
18
+ /**
19
+ * List all terms for a taxonomy
20
+ */
21
+ export const GET: APIRoute = async ({ params, locals }) => {
22
+ const { dineway, user } = locals;
23
+ const { name } = params;
24
+
25
+ if (!name) {
26
+ return apiError("VALIDATION_ERROR", "Taxonomy name required", 400);
27
+ }
28
+
29
+ const dbErr = requireDb(dineway?.db);
30
+ if (dbErr) return dbErr;
31
+
32
+ const denied = requirePerm(user, "taxonomies:read");
33
+ if (denied) return denied;
34
+
35
+ try {
36
+ const result = await handleTermList(dineway.db, name);
37
+ return unwrapResult(result);
38
+ } catch (error) {
39
+ return handleError(error, "Failed to list terms", "TERM_LIST_ERROR");
40
+ }
41
+ };
42
+
43
+ /**
44
+ * Create a new term
45
+ */
46
+ export const POST: APIRoute = async ({ params, request, locals }) => {
47
+ const { dineway, user } = locals;
48
+ const { name } = params;
49
+
50
+ if (!name) {
51
+ return apiError("VALIDATION_ERROR", "Taxonomy name required", 400);
52
+ }
53
+
54
+ const dbErr = requireDb(dineway?.db);
55
+ if (dbErr) return dbErr;
56
+
57
+ const denied = requirePerm(user, "taxonomies:manage");
58
+ if (denied) return denied;
59
+
60
+ try {
61
+ const body = await parseBody(request, createTermBody);
62
+ if (isParseError(body)) return body;
63
+
64
+ const result = await handleTermCreate(dineway.db, name, body);
65
+ return unwrapResult(result, 201);
66
+ } catch (error) {
67
+ return handleError(error, "Failed to create term", "TERM_CREATE_ERROR");
68
+ }
69
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Taxonomy definitions endpoint
3
+ *
4
+ * GET /_dineway/api/taxonomies - List all taxonomy definitions
5
+ * POST /_dineway/api/taxonomies - Create a custom taxonomy definition
6
+ */
7
+
8
+ import type { APIRoute } from "astro";
9
+
10
+ import { requirePerm } from "#api/authorize.js";
11
+ import { handleError, requireDb, unwrapResult } from "#api/error.js";
12
+ import { handleTaxonomyCreate, handleTaxonomyList } from "#api/handlers/taxonomies.js";
13
+ import { isParseError, parseBody } from "#api/parse.js";
14
+ import { createTaxonomyDefBody } from "#api/schemas.js";
15
+
16
+ export const prerender = false;
17
+
18
+ /**
19
+ * List taxonomy definitions
20
+ */
21
+ export const GET: APIRoute = async ({ locals }) => {
22
+ const { dineway, user } = locals;
23
+
24
+ const dbErr = requireDb(dineway?.db);
25
+ if (dbErr) return dbErr;
26
+
27
+ const denied = requirePerm(user, "taxonomies:read");
28
+ if (denied) return denied;
29
+
30
+ try {
31
+ const result = await handleTaxonomyList(dineway.db);
32
+ return unwrapResult(result);
33
+ } catch (error) {
34
+ return handleError(error, "Failed to list taxonomies", "TAXONOMY_LIST_ERROR");
35
+ }
36
+ };
37
+
38
+ /**
39
+ * Create a custom taxonomy definition
40
+ */
41
+ export const POST: APIRoute = async ({ request, locals }) => {
42
+ const { dineway, user } = locals;
43
+
44
+ const dbErr = requireDb(dineway?.db);
45
+ if (dbErr) return dbErr;
46
+
47
+ const denied = requirePerm(user, "taxonomies:manage");
48
+ if (denied) return denied;
49
+
50
+ try {
51
+ const body = await parseBody(request, createTaxonomyDefBody);
52
+ if (isParseError(body)) return body;
53
+
54
+ const result = await handleTaxonomyCreate(dineway.db, body);
55
+ return unwrapResult(result, 201);
56
+ } catch (error) {
57
+ return handleError(error, "Failed to create taxonomy", "TAXONOMY_CREATE_ERROR");
58
+ }
59
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Theme preview signing endpoint
3
+ *
4
+ * POST /_dineway/api/themes/preview
5
+ *
6
+ * Generates a signed preview URL for the "Try with my data" feature.
7
+ * The PREVIEW_SECRET must be set in the environment (shared with the preview sidecar).
8
+ */
9
+
10
+ import type { APIRoute } from "astro";
11
+
12
+ import { requirePerm } from "#api/authorize.js";
13
+ import { apiError, apiSuccess } from "#api/error.js";
14
+ import { getPublicOrigin } from "#api/public-url.js";
15
+
16
+ export const prerender = false;
17
+
18
+ export const POST: APIRoute = async ({ request, url, locals }) => {
19
+ const { dineway, user } = locals;
20
+
21
+ if (!dineway?.db) {
22
+ return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
23
+ }
24
+
25
+ const denied = requirePerm(user, "plugins:read");
26
+ if (denied) return denied;
27
+
28
+ const secret = import.meta.env.DINEWAY_PREVIEW_SECRET || import.meta.env.PREVIEW_SECRET || "";
29
+ if (!secret) {
30
+ return apiError("NOT_CONFIGURED", "PREVIEW_SECRET is not configured", 500);
31
+ }
32
+
33
+ let body: { previewUrl: string };
34
+ try {
35
+ body = await request.json();
36
+ } catch {
37
+ return apiError("INVALID_REQUEST", "Invalid JSON body", 400);
38
+ }
39
+
40
+ if (!body.previewUrl || typeof body.previewUrl !== "string") {
41
+ return apiError("INVALID_REQUEST", "previewUrl is required", 400);
42
+ }
43
+
44
+ // Validate previewUrl is a valid HTTPS URL
45
+ let parsedPreviewUrl: URL;
46
+ try {
47
+ parsedPreviewUrl = new URL(body.previewUrl);
48
+ } catch {
49
+ return apiError("INVALID_REQUEST", "previewUrl must be a valid URL", 400);
50
+ }
51
+
52
+ if (parsedPreviewUrl.protocol !== "https:") {
53
+ return apiError("INVALID_REQUEST", "previewUrl must use HTTPS", 400);
54
+ }
55
+
56
+ const source = getPublicOrigin(url, dineway?.config);
57
+ const ttl = 3600; // 1 hour
58
+ const exp = Math.floor(Date.now() / 1000) + ttl;
59
+
60
+ // HMAC-SHA256 sign: message = "source:exp"
61
+ const encoder = new TextEncoder();
62
+ const key = await crypto.subtle.importKey(
63
+ "raw",
64
+ encoder.encode(secret),
65
+ { name: "HMAC", hash: "SHA-256" },
66
+ false,
67
+ ["sign"],
68
+ );
69
+ const buffer = await crypto.subtle.sign("HMAC", key, encoder.encode(`${source}:${exp}`));
70
+ const sig = Array.from(new Uint8Array(buffer), (b) => b.toString(16).padStart(2, "0")).join("");
71
+
72
+ const previewUrl = new URL(body.previewUrl);
73
+ previewUrl.searchParams.set("source", source);
74
+ previewUrl.searchParams.set("exp", String(exp));
75
+ previewUrl.searchParams.set("sig", sig);
76
+
77
+ return apiSuccess({ url: previewUrl.toString() });
78
+ };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Typegen endpoint - generates dineway-env.d.ts content
3
+ *
4
+ * POST /_dineway/api/typegen - Generate types and return as JSON
5
+ * GET /_dineway/api/typegen - Return types as text (for preview/debugging)
6
+ *
7
+ * The caller (integration or CLI) is responsible for writing the file to disk.
8
+ * This endpoint only generates the content — it has no filesystem access, so it
9
+ * stays portable across runtimes where the project root or Node filesystem APIs
10
+ * are not available here.
11
+ *
12
+ * Dev-only endpoint - disabled in production.
13
+ */
14
+
15
+ import type { APIRoute } from "astro";
16
+
17
+ import { apiError, apiSuccess, handleError } from "#api/error.js";
18
+ import type { SchemaRegistry } from "#schema/registry.js";
19
+
20
+ export const prerender = false;
21
+
22
+ /**
23
+ * Safely list collections, returning empty array if tables don't exist yet
24
+ */
25
+ async function safeListCollections(registry: SchemaRegistry) {
26
+ try {
27
+ return await registry.listCollections();
28
+ } catch (error) {
29
+ // Handle missing tables for new sites that haven't run setup yet
30
+ if (error instanceof Error && error.message.includes("no such table")) {
31
+ return [];
32
+ }
33
+ throw error;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Generate types content and metadata from the current schema.
39
+ */
40
+ async function generateTypes(registry: SchemaRegistry) {
41
+ const { generateTypesFile, generateSchemaHash } = await import("#schema/zod-generator.js");
42
+
43
+ const collections = await safeListCollections(registry);
44
+ const collectionsWithFields = await Promise.all(
45
+ collections.map(async (c) => {
46
+ const fields = await registry.listFields(c.id);
47
+ return { ...c, fields };
48
+ }),
49
+ );
50
+
51
+ const types = generateTypesFile(collectionsWithFields);
52
+ const hash: string = await generateSchemaHash(collectionsWithFields);
53
+
54
+ return { types, hash, collections: collections.length };
55
+ }
56
+
57
+ /**
58
+ * GET - Return types as plain text (for preview/debugging)
59
+ */
60
+ export const GET: APIRoute = async ({ locals }) => {
61
+ if (!import.meta.env.DEV) {
62
+ return apiError("FORBIDDEN", "Typegen is only available in development", 403);
63
+ }
64
+
65
+ const { dineway } = locals;
66
+
67
+ if (!dineway?.db) {
68
+ return apiError("NOT_CONFIGURED", "Dineway is not configured", 500);
69
+ }
70
+
71
+ try {
72
+ const { SchemaRegistry } = await import("#schema/registry.js");
73
+ const registry = new SchemaRegistry(dineway.db);
74
+ const { types } = await generateTypes(registry);
75
+
76
+ return new Response(types, {
77
+ status: 200,
78
+ headers: {
79
+ "Content-Type": "text/typescript",
80
+ "Cache-Control": "private, no-store",
81
+ },
82
+ });
83
+ } catch (error) {
84
+ return handleError(error, "Typegen failed", "TYPEGEN_ERROR");
85
+ }
86
+ };
87
+
88
+ /**
89
+ * POST - Generate types and return as JSON
90
+ *
91
+ * The caller writes the file to disk. Response shape:
92
+ * { types: string, hash: string, collections: number }
93
+ */
94
+ export const POST: APIRoute = async ({ locals }) => {
95
+ if (!import.meta.env.DEV) {
96
+ return apiError("FORBIDDEN", "Typegen is only available in development", 403);
97
+ }
98
+
99
+ const { dineway } = locals;
100
+
101
+ if (!dineway?.db) {
102
+ return apiError("NOT_CONFIGURED", "Dineway is not configured", 500);
103
+ }
104
+
105
+ try {
106
+ const { SchemaRegistry } = await import("#schema/registry.js");
107
+ const registry = new SchemaRegistry(dineway.db);
108
+ const result = await generateTypes(registry);
109
+
110
+ return apiSuccess(result);
111
+ } catch (error) {
112
+ return handleError(error, "Typegen failed", "TYPEGEN_ERROR");
113
+ }
114
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * GET /_dineway/.well-known/auth
3
+ *
4
+ * Auth discovery endpoint. Returns available auth mechanisms.
5
+ * Public, unauthenticated. Used by CLI to determine how to authenticate.
6
+ */
7
+
8
+ import type { APIRoute } from "astro";
9
+
10
+ import { getAuthMode } from "#auth/mode.js";
11
+ import { OptionsRepository } from "#db/repositories/options.js";
12
+
13
+ export const prerender = false;
14
+
15
+ export const GET: APIRoute = async ({ locals }) => {
16
+ const { dineway } = locals;
17
+
18
+ // Build discovery response
19
+ const config = dineway?.config;
20
+ const authMode = config ? getAuthMode(config) : null;
21
+
22
+ const isExternal = authMode?.type === "external";
23
+
24
+ // Try to read site name from DB options
25
+ let siteName = "Dineway";
26
+ if (dineway?.db) {
27
+ try {
28
+ const options = new OptionsRepository(dineway.db);
29
+ siteName = (await options.get<string>("dineway:site_title")) || "Dineway";
30
+ } catch {
31
+ // DB may not be initialized yet
32
+ }
33
+ }
34
+
35
+ const response: Record<string, unknown> = {
36
+ instance: {
37
+ name: siteName,
38
+ version: "0.1.0",
39
+ },
40
+ auth: {
41
+ mode: isExternal ? "external" : "passkey",
42
+ ...(isExternal && authMode.type === "external"
43
+ ? { external_provider: authMode.entrypoint }
44
+ : {}),
45
+ methods: {
46
+ device_flow: !isExternal
47
+ ? {
48
+ client_id: "dineway-cli",
49
+ device_authorization_endpoint: "/_dineway/api/oauth/device/code",
50
+ token_endpoint: "/_dineway/api/oauth/device/token",
51
+ }
52
+ : undefined,
53
+ authorization_code: !isExternal
54
+ ? {
55
+ authorization_endpoint: "/_dineway/oauth/authorize",
56
+ token_endpoint: "/_dineway/api/oauth/token",
57
+ }
58
+ : undefined,
59
+ api_tokens: true,
60
+ },
61
+ },
62
+ };
63
+
64
+ return Response.json(response, {
65
+ headers: {
66
+ "Cache-Control": "no-store",
67
+ },
68
+ });
69
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * GET /_dineway/.well-known/oauth-authorization-server
3
+ *
4
+ * RFC 8414 Authorization Server Metadata. Tells MCP clients which
5
+ * endpoints to use for OAuth authorization, token exchange, etc.
6
+ *
7
+ * Public, unauthenticated.
8
+ */
9
+
10
+ import type { APIRoute } from "astro";
11
+
12
+ import { getPublicOrigin } from "#api/public-url.js";
13
+ import { VALID_SCOPES } from "#auth/api-tokens.js";
14
+
15
+ export const prerender = false;
16
+
17
+ export const GET: APIRoute = async ({ url, locals }) => {
18
+ const origin = getPublicOrigin(url, locals.dineway?.config);
19
+ const issuer = `${origin}/_dineway`;
20
+
21
+ return Response.json(
22
+ {
23
+ issuer,
24
+ authorization_endpoint: `${origin}/_dineway/oauth/authorize`,
25
+ token_endpoint: `${origin}/_dineway/api/oauth/token`,
26
+ scopes_supported: [...VALID_SCOPES],
27
+ response_types_supported: ["code"],
28
+ grant_types_supported: [
29
+ "authorization_code",
30
+ "refresh_token",
31
+ "urn:ietf:params:oauth:grant-type:device_code",
32
+ ],
33
+ code_challenge_methods_supported: ["S256"],
34
+ token_endpoint_auth_methods_supported: ["none"],
35
+ client_id_metadata_document_supported: true,
36
+ device_authorization_endpoint: `${origin}/_dineway/api/oauth/device/code`,
37
+ },
38
+ {
39
+ headers: {
40
+ "Cache-Control": "public, max-age=3600",
41
+ "Access-Control-Allow-Origin": "*",
42
+ },
43
+ },
44
+ );
45
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * GET /.well-known/oauth-protected-resource
3
+ *
4
+ * RFC 9728 Protected Resource Metadata. Tells MCP clients where to find
5
+ * the authorization server. Injected at the site root (not under /_dineway/)
6
+ * because RFC 9728 requires it at the well-known URI of the resource's origin.
7
+ *
8
+ * Also serves as `/.well-known/oauth-protected-resource/_dineway/api/mcp`
9
+ * (path-scoped variant) when Astro's routing allows.
10
+ *
11
+ * Public, unauthenticated.
12
+ */
13
+
14
+ import type { APIRoute } from "astro";
15
+
16
+ import { getPublicOrigin } from "#api/public-url.js";
17
+ import { VALID_SCOPES } from "#auth/api-tokens.js";
18
+
19
+ export const prerender = false;
20
+
21
+ export const GET: APIRoute = async ({ url, locals }) => {
22
+ const origin = getPublicOrigin(url, locals.dineway?.config);
23
+
24
+ return Response.json(
25
+ {
26
+ resource: `${origin}/_dineway/api/mcp`,
27
+ authorization_servers: [`${origin}/_dineway`],
28
+ scopes_supported: [...VALID_SCOPES],
29
+ bearer_methods_supported: ["header"],
30
+ },
31
+ {
32
+ headers: {
33
+ "Cache-Control": "public, max-age=3600",
34
+ "Access-Control-Allow-Origin": "*",
35
+ },
36
+ },
37
+ );
38
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Reorder widgets endpoint
3
+ *
4
+ * POST /_dineway/api/widget-areas/:name/reorder
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ import { requirePerm } from "#api/authorize.js";
10
+ import { apiError, apiSuccess, handleError } from "#api/error.js";
11
+ import { isParseError, parseBody } from "#api/parse.js";
12
+ import { reorderWidgetsBody } from "#api/schemas.js";
13
+
14
+ export const prerender = false;
15
+
16
+ export const POST: APIRoute = async ({ params, request, locals }) => {
17
+ const { dineway, user } = locals;
18
+ const db = dineway.db;
19
+ const { name } = params;
20
+
21
+ const denied = requirePerm(user, "widgets:manage");
22
+ if (denied) return denied;
23
+
24
+ if (!name) {
25
+ return apiError("VALIDATION_ERROR", "name is required", 400);
26
+ }
27
+
28
+ try {
29
+ // Get the area
30
+ const area = await db
31
+ .selectFrom("_dineway_widget_areas")
32
+ .select("id")
33
+ .where("name", "=", name)
34
+ .executeTakeFirst();
35
+
36
+ if (!area) {
37
+ return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
38
+ }
39
+
40
+ const body = await parseBody(request, reorderWidgetsBody);
41
+ if (isParseError(body)) return body;
42
+
43
+ // Verify all widget IDs belong to this area
44
+ const existingWidgets = await db
45
+ .selectFrom("_dineway_widgets")
46
+ .select("id")
47
+ .where("area_id", "=", area.id)
48
+ .execute();
49
+
50
+ const existingIds = new Set(existingWidgets.map((w) => w.id));
51
+ for (const id of body.widgetIds) {
52
+ if (!existingIds.has(id)) {
53
+ return apiError("VALIDATION_ERROR", `Widget "${id}" not found in area "${name}"`, 400);
54
+ }
55
+ }
56
+
57
+ // Update sort_order for each widget
58
+ await Promise.all(
59
+ body.widgetIds.map((id, index) =>
60
+ db
61
+ .updateTable("_dineway_widgets")
62
+ .set({ sort_order: index })
63
+ .where("id", "=", id)
64
+ .execute(),
65
+ ),
66
+ );
67
+
68
+ return apiSuccess({ success: true });
69
+ } catch (error) {
70
+ return handleError(error, "Failed to reorder widgets", "WIDGET_REORDER_ERROR");
71
+ }
72
+ };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Single widget endpoints
3
+ *
4
+ * PUT /_dineway/api/widget-areas/:name/widgets/:id - Update widget
5
+ * DELETE /_dineway/api/widget-areas/:name/widgets/:id - Delete widget
6
+ */
7
+
8
+ import type { APIRoute } from "astro";
9
+
10
+ import { requirePerm } from "#api/authorize.js";
11
+ import { apiError, apiSuccess, handleError } from "#api/error.js";
12
+ import { isParseError, parseBody } from "#api/parse.js";
13
+ import { updateWidgetBody } from "#api/schemas.js";
14
+
15
+ export const prerender = false;
16
+
17
+ export const PUT: APIRoute = async ({ params, request, locals }) => {
18
+ const { dineway, user } = locals;
19
+ const db = dineway.db;
20
+ const { name, id } = params;
21
+
22
+ const denied = requirePerm(user, "widgets:manage");
23
+ if (denied) return denied;
24
+
25
+ if (!name || !id) {
26
+ return apiError("VALIDATION_ERROR", "name and id are required", 400);
27
+ }
28
+
29
+ try {
30
+ // Get the area
31
+ const area = await db
32
+ .selectFrom("_dineway_widget_areas")
33
+ .select("id")
34
+ .where("name", "=", name)
35
+ .executeTakeFirst();
36
+
37
+ if (!area) {
38
+ return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
39
+ }
40
+
41
+ // Check widget exists and belongs to this area
42
+ const existingWidget = await db
43
+ .selectFrom("_dineway_widgets")
44
+ .select("id")
45
+ .where("id", "=", id)
46
+ .where("area_id", "=", area.id)
47
+ .executeTakeFirst();
48
+
49
+ if (!existingWidget) {
50
+ return apiError("NOT_FOUND", `Widget "${id}" not found in area "${name}"`, 404);
51
+ }
52
+
53
+ const body = await parseBody(request, updateWidgetBody);
54
+ if (isParseError(body)) return body;
55
+
56
+ // Build update object (only update provided fields)
57
+ const updates: Record<string, unknown> = {};
58
+ if (body.title !== undefined) updates.title = body.title || null;
59
+ if (body.type !== undefined) updates.type = body.type;
60
+ if (body.content !== undefined)
61
+ updates.content = body.content ? JSON.stringify(body.content) : null;
62
+ if (body.menuName !== undefined) updates.menu_name = body.menuName || null;
63
+ if (body.componentId !== undefined) updates.component_id = body.componentId || null;
64
+ if (body.componentProps !== undefined)
65
+ updates.component_props = body.componentProps ? JSON.stringify(body.componentProps) : null;
66
+
67
+ if (Object.keys(updates).length === 0) {
68
+ return apiError("VALIDATION_ERROR", "No fields to update", 400);
69
+ }
70
+
71
+ await db.updateTable("_dineway_widgets").set(updates).where("id", "=", id).execute();
72
+
73
+ const widget = await db
74
+ .selectFrom("_dineway_widgets")
75
+ .selectAll()
76
+ .where("id", "=", id)
77
+ .executeTakeFirstOrThrow();
78
+
79
+ return apiSuccess(widget);
80
+ } catch (error) {
81
+ return handleError(error, "Failed to update widget", "WIDGET_UPDATE_ERROR");
82
+ }
83
+ };
84
+
85
+ export const DELETE: APIRoute = async ({ params, locals }) => {
86
+ const { dineway, user } = locals;
87
+ const db = dineway.db;
88
+ const { name, id } = params;
89
+
90
+ const denied = requirePerm(user, "widgets:manage");
91
+ if (denied) return denied;
92
+
93
+ if (!name || !id) {
94
+ return apiError("VALIDATION_ERROR", "name and id are required", 400);
95
+ }
96
+
97
+ try {
98
+ // Get the area
99
+ const area = await db
100
+ .selectFrom("_dineway_widget_areas")
101
+ .select("id")
102
+ .where("name", "=", name)
103
+ .executeTakeFirst();
104
+
105
+ if (!area) {
106
+ return apiError("NOT_FOUND", `Widget area "${name}" not found`, 404);
107
+ }
108
+
109
+ // Check widget exists and belongs to this area
110
+ const existingWidget = await db
111
+ .selectFrom("_dineway_widgets")
112
+ .select("id")
113
+ .where("id", "=", id)
114
+ .where("area_id", "=", area.id)
115
+ .executeTakeFirst();
116
+
117
+ if (!existingWidget) {
118
+ return apiError("NOT_FOUND", `Widget "${id}" not found in area "${name}"`, 404);
119
+ }
120
+
121
+ await db.deleteFrom("_dineway_widgets").where("id", "=", id).execute();
122
+
123
+ return apiSuccess({ deleted: true });
124
+ } catch (error) {
125
+ return handleError(error, "Failed to delete widget", "WIDGET_DELETE_ERROR");
126
+ }
127
+ };