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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment status change
|
|
3
|
+
*
|
|
4
|
+
* PUT /_dineway/api/admin/comments/:id/status - Change comment status
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, apiSuccess, handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleCommentGet } from "#api/handlers/comments.js";
|
|
12
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
13
|
+
import { commentStatusBody } from "#api/schemas.js";
|
|
14
|
+
import { getSiteBaseUrl } from "#api/site-url.js";
|
|
15
|
+
import { lookupContentAuthor, sendCommentNotification } from "#comments/notifications.js";
|
|
16
|
+
import { moderateComment, type CommentHookRunner } from "#comments/service.js";
|
|
17
|
+
import type { CommentStatus } from "#db/repositories/comment.js";
|
|
18
|
+
import type { ModerationDecision } from "#plugins/types.js";
|
|
19
|
+
|
|
20
|
+
export const prerender = false;
|
|
21
|
+
|
|
22
|
+
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
23
|
+
const { dineway, user } = locals;
|
|
24
|
+
const { id } = params;
|
|
25
|
+
|
|
26
|
+
if (!id) {
|
|
27
|
+
return apiError("VALIDATION_ERROR", "Comment ID required", 400);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const dbErr = requireDb(dineway?.db);
|
|
31
|
+
if (dbErr) return dbErr;
|
|
32
|
+
|
|
33
|
+
const denied = requirePerm(user, "comments:moderate");
|
|
34
|
+
if (denied) return denied;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const body = await parseBody(request, commentStatusBody);
|
|
38
|
+
if (isParseError(body)) return body;
|
|
39
|
+
|
|
40
|
+
const newStatus = body.status as CommentStatus;
|
|
41
|
+
|
|
42
|
+
// Build hook runner for the service
|
|
43
|
+
const hookRunner: CommentHookRunner = {
|
|
44
|
+
async runBeforeCreate(event) {
|
|
45
|
+
return dineway.hooks.runCommentBeforeCreate(event);
|
|
46
|
+
},
|
|
47
|
+
async runModerate(event) {
|
|
48
|
+
const result = await dineway.hooks.invokeExclusiveHook("comment:moderate", event);
|
|
49
|
+
if (!result) return { status: "pending" as const, reason: "No moderator configured" };
|
|
50
|
+
if (result.error) return { status: "pending" as const, reason: "Moderation error" };
|
|
51
|
+
return result.result as ModerationDecision;
|
|
52
|
+
},
|
|
53
|
+
fireAfterCreate(event) {
|
|
54
|
+
dineway.hooks
|
|
55
|
+
.runCommentAfterCreate(event)
|
|
56
|
+
.catch((err) =>
|
|
57
|
+
console.error(
|
|
58
|
+
"[comments] afterCreate error:",
|
|
59
|
+
err instanceof Error ? err.message : err,
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
fireAfterModerate(event) {
|
|
64
|
+
dineway.hooks
|
|
65
|
+
.runCommentAfterModerate(event)
|
|
66
|
+
.catch((err) =>
|
|
67
|
+
console.error(
|
|
68
|
+
"[comments] afterModerate error:",
|
|
69
|
+
err instanceof Error ? err.message : err,
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Read the comment before updating so we know the previous status
|
|
76
|
+
const existing = await handleCommentGet(dineway.db, id);
|
|
77
|
+
if (!existing.success) {
|
|
78
|
+
return unwrapResult(existing);
|
|
79
|
+
}
|
|
80
|
+
const previousStatus = existing.data.status;
|
|
81
|
+
|
|
82
|
+
const updated = await moderateComment(
|
|
83
|
+
dineway.db,
|
|
84
|
+
id,
|
|
85
|
+
newStatus,
|
|
86
|
+
{ id: user!.id, name: user!.name ?? null },
|
|
87
|
+
hookRunner,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (!updated) {
|
|
91
|
+
return apiError("NOT_FOUND", "Comment not found", 404);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Send notification when a comment is newly approved
|
|
95
|
+
if (newStatus === "approved" && previousStatus !== "approved" && dineway.email) {
|
|
96
|
+
try {
|
|
97
|
+
const adminBaseUrl = await getSiteBaseUrl(dineway.db, request);
|
|
98
|
+
const content = await lookupContentAuthor(
|
|
99
|
+
dineway.db,
|
|
100
|
+
updated.collection,
|
|
101
|
+
updated.contentId,
|
|
102
|
+
);
|
|
103
|
+
if (content?.author) {
|
|
104
|
+
await sendCommentNotification({
|
|
105
|
+
email: dineway.email,
|
|
106
|
+
comment: updated,
|
|
107
|
+
contentAuthor: content.author,
|
|
108
|
+
adminBaseUrl,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error("[comments] notification error:", err instanceof Error ? err.message : err);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return apiSuccess(updated);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return handleError(error, "Failed to update comment status", "COMMENT_STATUS_ERROR");
|
|
119
|
+
}
|
|
120
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single comment admin endpoints
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/comments/:id - Get comment detail
|
|
5
|
+
* DELETE /_dineway/api/admin/comments/:id - Hard delete (ADMIN only)
|
|
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 { handleCommentGet, handleCommentDelete } from "#api/handlers/comments.js";
|
|
13
|
+
|
|
14
|
+
export const prerender = false;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get single comment detail (includes moderation_metadata)
|
|
18
|
+
*/
|
|
19
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
20
|
+
const { dineway, user } = locals;
|
|
21
|
+
const { id } = params;
|
|
22
|
+
|
|
23
|
+
if (!id) {
|
|
24
|
+
return apiError("VALIDATION_ERROR", "Comment ID required", 400);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const dbErr = requireDb(dineway?.db);
|
|
28
|
+
if (dbErr) return dbErr;
|
|
29
|
+
|
|
30
|
+
const denied = requirePerm(user, "comments:moderate");
|
|
31
|
+
if (denied) return denied;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const result = await handleCommentGet(dineway.db, id);
|
|
35
|
+
return unwrapResult(result);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return handleError(error, "Failed to get comment", "COMMENT_GET_ERROR");
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Hard delete a comment (ADMIN only)
|
|
43
|
+
*/
|
|
44
|
+
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
45
|
+
const { dineway, user } = locals;
|
|
46
|
+
const { id } = params;
|
|
47
|
+
|
|
48
|
+
if (!id) {
|
|
49
|
+
return apiError("VALIDATION_ERROR", "Comment ID required", 400);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const dbErr = requireDb(dineway?.db);
|
|
53
|
+
if (dbErr) return dbErr;
|
|
54
|
+
|
|
55
|
+
const denied = requirePerm(user, "comments:delete");
|
|
56
|
+
if (denied) return denied;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const result = await handleCommentDelete(dineway.db, id);
|
|
60
|
+
return unwrapResult(result);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return handleError(error, "Failed to delete comment", "COMMENT_DELETE_ERROR");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk comment operations
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/admin/comments/bulk - Bulk status change or delete
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleCommentBulk } from "#api/handlers/comments.js";
|
|
12
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
13
|
+
import { commentBulkBody } from "#api/schemas.js";
|
|
14
|
+
|
|
15
|
+
export const prerender = false;
|
|
16
|
+
|
|
17
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
18
|
+
const { dineway, user } = locals;
|
|
19
|
+
|
|
20
|
+
const dbErr = requireDb(dineway?.db);
|
|
21
|
+
if (dbErr) return dbErr;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const body = await parseBody(request, commentBulkBody);
|
|
25
|
+
if (isParseError(body)) return body;
|
|
26
|
+
|
|
27
|
+
// Bulk delete requires ADMIN, bulk status change requires EDITOR
|
|
28
|
+
if (body.action === "delete") {
|
|
29
|
+
const denied = requirePerm(user, "comments:delete");
|
|
30
|
+
if (denied) return denied;
|
|
31
|
+
} else {
|
|
32
|
+
const denied = requirePerm(user, "comments:moderate");
|
|
33
|
+
if (denied) return denied;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const result = await handleCommentBulk(dineway.db, body.ids, body.action);
|
|
37
|
+
|
|
38
|
+
return unwrapResult(result);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return handleError(error, "Failed to perform bulk operation", "COMMENT_BULK_ERROR");
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment status counts for inbox badges
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/comments/counts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleCommentCounts } from "#api/handlers/comments.js";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
16
|
+
const { dineway, user } = locals;
|
|
17
|
+
|
|
18
|
+
const dbErr = requireDb(dineway?.db);
|
|
19
|
+
if (dbErr) return dbErr;
|
|
20
|
+
|
|
21
|
+
const denied = requirePerm(user, "comments:moderate");
|
|
22
|
+
if (denied) return denied;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const result = await handleCommentCounts(dineway.db);
|
|
26
|
+
return unwrapResult(result);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return handleError(error, "Failed to get comment counts", "COMMENT_COUNTS_ERROR");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin comment inbox
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/comments - List comments (filterable by status, collection, search)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleCommentInbox } from "#api/handlers/comments.js";
|
|
12
|
+
import { isParseError, parseQuery } from "#api/parse.js";
|
|
13
|
+
import { commentListQuery } from "#api/schemas.js";
|
|
14
|
+
import type { CommentStatus } from "#db/repositories/comment.js";
|
|
15
|
+
|
|
16
|
+
export const prerender = false;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* List comments for moderation inbox
|
|
20
|
+
*/
|
|
21
|
+
export const GET: APIRoute = async ({ url, locals }) => {
|
|
22
|
+
const { dineway, user } = locals;
|
|
23
|
+
|
|
24
|
+
const dbErr = requireDb(dineway?.db);
|
|
25
|
+
if (dbErr) return dbErr;
|
|
26
|
+
|
|
27
|
+
const denied = requirePerm(user, "comments:moderate");
|
|
28
|
+
if (denied) return denied;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const query = parseQuery(url, commentListQuery);
|
|
32
|
+
if (isParseError(query)) return query;
|
|
33
|
+
|
|
34
|
+
const result = await handleCommentInbox(dineway.db, {
|
|
35
|
+
status: query.status as CommentStatus | undefined,
|
|
36
|
+
collection: query.collection,
|
|
37
|
+
search: query.search,
|
|
38
|
+
limit: query.limit,
|
|
39
|
+
cursor: query.cursor,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return unwrapResult(result);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return handleError(error, "Failed to list comments", "COMMENT_INBOX_ERROR");
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exclusive hook selection endpoint
|
|
3
|
+
*
|
|
4
|
+
* PUT /_dineway/api/admin/hooks/exclusive/:hookName
|
|
5
|
+
*
|
|
6
|
+
* Sets or clears the selected provider for an exclusive hook.
|
|
7
|
+
* Body: { pluginId: string | null }
|
|
8
|
+
* Requires settings:manage permission.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { APIRoute } from "astro";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
import { requirePerm } from "#api/authorize.js";
|
|
15
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
16
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
17
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
18
|
+
|
|
19
|
+
export const prerender = false;
|
|
20
|
+
|
|
21
|
+
/** Hook name format: namespace:action (e.g., "content:beforeSave") */
|
|
22
|
+
const HOOK_NAME_RE = /^[a-z]+:[a-zA-Z]+$/;
|
|
23
|
+
|
|
24
|
+
const setSelectionSchema = z.object({
|
|
25
|
+
pluginId: z.string().min(1).nullable(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
29
|
+
const { dineway, user } = locals;
|
|
30
|
+
|
|
31
|
+
if (!dineway?.db) {
|
|
32
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const denied = requirePerm(user, "settings:manage");
|
|
36
|
+
if (denied) return denied;
|
|
37
|
+
|
|
38
|
+
const hookName = params.hookName;
|
|
39
|
+
if (!hookName) {
|
|
40
|
+
return apiError("VALIDATION_ERROR", "Hook name is required", 400);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Validate hook name format: must be namespace:action (e.g., "content:beforeSave")
|
|
44
|
+
if (!HOOK_NAME_RE.test(hookName)) {
|
|
45
|
+
return apiError("VALIDATION_ERROR", "Invalid hook name format", 400);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const pipeline = dineway.hooks;
|
|
50
|
+
|
|
51
|
+
// Verify this is actually an exclusive hook
|
|
52
|
+
if (!pipeline.isExclusiveHook(hookName)) {
|
|
53
|
+
return apiError("NOT_FOUND", `Hook '${hookName}' is not a registered exclusive hook`, 404);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const body = await parseBody(request, setSelectionSchema);
|
|
57
|
+
if (isParseError(body)) return body;
|
|
58
|
+
|
|
59
|
+
const optionsRepo = new OptionsRepository(dineway.db);
|
|
60
|
+
const optionKey = `dineway:exclusive_hook:${hookName}`;
|
|
61
|
+
|
|
62
|
+
if (body.pluginId === null) {
|
|
63
|
+
// Clear the selection
|
|
64
|
+
await optionsRepo.delete(optionKey);
|
|
65
|
+
pipeline.clearExclusiveSelection(hookName);
|
|
66
|
+
} else {
|
|
67
|
+
// Validate that the pluginId is an actual provider for this hook
|
|
68
|
+
const providers = pipeline.getExclusiveHookProviders(hookName);
|
|
69
|
+
const isValidProvider = providers.some(
|
|
70
|
+
(p: { pluginId: string }) => p.pluginId === body.pluginId,
|
|
71
|
+
);
|
|
72
|
+
if (!isValidProvider) {
|
|
73
|
+
return apiError(
|
|
74
|
+
"VALIDATION_ERROR",
|
|
75
|
+
`Plugin '${body.pluginId}' is not a provider for hook '${hookName}'`,
|
|
76
|
+
400,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await optionsRepo.set(optionKey, body.pluginId);
|
|
81
|
+
pipeline.setExclusiveSelection(hookName, body.pluginId);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return apiSuccess({
|
|
85
|
+
hookName,
|
|
86
|
+
selectedPluginId: body.pluginId,
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return handleError(error, "Failed to set exclusive hook selection", "EXCLUSIVE_HOOK_SET_ERROR");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exclusive hooks list endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/hooks/exclusive
|
|
5
|
+
*
|
|
6
|
+
* Lists all exclusive hooks with their providers and current selections.
|
|
7
|
+
* Requires admin role.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { APIRoute } from "astro";
|
|
11
|
+
|
|
12
|
+
import { requirePerm } from "#api/authorize.js";
|
|
13
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
14
|
+
import { OptionsRepository } from "#db/repositories/options.js";
|
|
15
|
+
|
|
16
|
+
export const prerender = false;
|
|
17
|
+
|
|
18
|
+
export const GET: APIRoute = async ({ 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, "settings:manage");
|
|
26
|
+
if (denied) return denied;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const pipeline = dineway.hooks;
|
|
30
|
+
const exclusiveHookNames = pipeline.getRegisteredExclusiveHooks();
|
|
31
|
+
const optionsRepo = new OptionsRepository(dineway.db);
|
|
32
|
+
|
|
33
|
+
const hooks = [];
|
|
34
|
+
for (const hookName of exclusiveHookNames) {
|
|
35
|
+
const providers = pipeline.getExclusiveHookProviders(hookName);
|
|
36
|
+
const selection = await optionsRepo.get<string>(`dineway:exclusive_hook:${hookName}`);
|
|
37
|
+
|
|
38
|
+
hooks.push({
|
|
39
|
+
hookName,
|
|
40
|
+
providers: providers.map((provider: { pluginId: string }) => ({
|
|
41
|
+
pluginId: provider.pluginId,
|
|
42
|
+
})),
|
|
43
|
+
selectedPluginId: selection,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return apiSuccess({ items: hooks });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return handleError(error, "Failed to list exclusive hooks", "EXCLUSIVE_HOOKS_LIST_ERROR");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single OAuth client endpoints
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/oauth-clients/:id — Get a client
|
|
5
|
+
* PUT /_dineway/api/admin/oauth-clients/:id — Update a client
|
|
6
|
+
* DELETE /_dineway/api/admin/oauth-clients/:id — Delete a client
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Role } from "@dineway-ai/auth";
|
|
10
|
+
import type { APIRoute } from "astro";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import { apiError, handleError, unwrapResult } from "#api/error.js";
|
|
14
|
+
import {
|
|
15
|
+
handleOAuthClientDelete,
|
|
16
|
+
handleOAuthClientGet,
|
|
17
|
+
handleOAuthClientUpdate,
|
|
18
|
+
} from "#api/handlers/oauth-clients.js";
|
|
19
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
20
|
+
|
|
21
|
+
export const prerender = false;
|
|
22
|
+
|
|
23
|
+
const updateClientSchema = z.object({
|
|
24
|
+
name: z.string().min(1).max(255).optional(),
|
|
25
|
+
redirectUris: z
|
|
26
|
+
.array(z.string().url("Each redirect URI must be a valid URL"))
|
|
27
|
+
.min(1, "At least one redirect URI is required")
|
|
28
|
+
.optional(),
|
|
29
|
+
scopes: z.array(z.string()).nullable().optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get a single OAuth client.
|
|
34
|
+
*/
|
|
35
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
36
|
+
const { dineway, user } = locals;
|
|
37
|
+
|
|
38
|
+
if (!dineway?.db) {
|
|
39
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!user || user.role < Role.ADMIN) {
|
|
43
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const clientId = params.id;
|
|
47
|
+
if (!clientId) {
|
|
48
|
+
return apiError("VALIDATION_ERROR", "Client ID is required", 400);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = await handleOAuthClientGet(dineway.db, clientId);
|
|
52
|
+
return unwrapResult(result);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Update an OAuth client.
|
|
57
|
+
*/
|
|
58
|
+
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
59
|
+
const { dineway, user } = locals;
|
|
60
|
+
|
|
61
|
+
if (!dineway?.db) {
|
|
62
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!user || user.role < Role.ADMIN) {
|
|
66
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const clientId = params.id;
|
|
70
|
+
if (!clientId) {
|
|
71
|
+
return apiError("VALIDATION_ERROR", "Client ID is required", 400);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const body = await parseBody(request, updateClientSchema);
|
|
76
|
+
if (isParseError(body)) return body;
|
|
77
|
+
|
|
78
|
+
const result = await handleOAuthClientUpdate(dineway.db, clientId, body);
|
|
79
|
+
return unwrapResult(result);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return handleError(error, "Failed to update OAuth client", "CLIENT_UPDATE_ERROR");
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Delete an OAuth client.
|
|
87
|
+
*/
|
|
88
|
+
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
89
|
+
const { dineway, user } = locals;
|
|
90
|
+
|
|
91
|
+
if (!dineway?.db) {
|
|
92
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!user || user.role < Role.ADMIN) {
|
|
96
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const clientId = params.id;
|
|
100
|
+
if (!clientId) {
|
|
101
|
+
return apiError("VALIDATION_ERROR", "Client ID is required", 400);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const result = await handleOAuthClientDelete(dineway.db, clientId);
|
|
106
|
+
return unwrapResult(result);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return handleError(error, "Failed to delete OAuth client", "CLIENT_DELETE_ERROR");
|
|
109
|
+
}
|
|
110
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth client management endpoints
|
|
3
|
+
*
|
|
4
|
+
* GET /_dineway/api/admin/oauth-clients — List all registered OAuth clients
|
|
5
|
+
* POST /_dineway/api/admin/oauth-clients — Register a new OAuth client
|
|
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 { handleOAuthClientCreate, handleOAuthClientList } from "#api/handlers/oauth-clients.js";
|
|
14
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
15
|
+
|
|
16
|
+
export const prerender = false;
|
|
17
|
+
|
|
18
|
+
const createClientSchema = z.object({
|
|
19
|
+
id: z
|
|
20
|
+
.string()
|
|
21
|
+
.min(1, "Client ID is required")
|
|
22
|
+
.max(255, "Client ID must be at most 255 characters"),
|
|
23
|
+
name: z.string().min(1, "Name is required").max(255, "Name must be at most 255 characters"),
|
|
24
|
+
redirectUris: z
|
|
25
|
+
.array(z.string().url("Each redirect URI must be a valid URL"))
|
|
26
|
+
.min(1, "At least one redirect URI is required"),
|
|
27
|
+
scopes: z.array(z.string()).optional(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* List all registered OAuth clients.
|
|
32
|
+
*/
|
|
33
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
34
|
+
const { dineway, user } = locals;
|
|
35
|
+
|
|
36
|
+
if (!dineway?.db) {
|
|
37
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!user || user.role < Role.ADMIN) {
|
|
41
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const result = await handleOAuthClientList(dineway.db);
|
|
45
|
+
return unwrapResult(result);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Register a new OAuth client.
|
|
50
|
+
*/
|
|
51
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
52
|
+
const { dineway, user } = locals;
|
|
53
|
+
|
|
54
|
+
if (!dineway?.db) {
|
|
55
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!user || user.role < Role.ADMIN) {
|
|
59
|
+
return apiError("FORBIDDEN", "Admin privileges required", 403);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const body = await parseBody(request, createClientSchema);
|
|
64
|
+
if (isParseError(body)) return body;
|
|
65
|
+
|
|
66
|
+
const result = await handleOAuthClientCreate(dineway.db, body);
|
|
67
|
+
return unwrapResult(result, 201);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return handleError(error, "Failed to create OAuth client", "CLIENT_CREATE_ERROR");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin disable endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_dineway/api/admin/plugins/:id/disable - Disable a plugin
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handlePluginDisable } from "#api/index.js";
|
|
12
|
+
import { setCronTasksEnabled } from "#plugins/cron.js";
|
|
13
|
+
|
|
14
|
+
export const prerender = false;
|
|
15
|
+
|
|
16
|
+
export const POST: APIRoute = async ({ params, locals }) => {
|
|
17
|
+
const { dineway, user } = locals;
|
|
18
|
+
const { id } = params;
|
|
19
|
+
|
|
20
|
+
if (!dineway?.db) {
|
|
21
|
+
return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const denied = requirePerm(user, "plugins:manage");
|
|
25
|
+
if (denied) return denied;
|
|
26
|
+
|
|
27
|
+
if (!id) {
|
|
28
|
+
return apiError("INVALID_REQUEST", "Plugin ID required", 400);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const result = await handlePluginDisable(dineway.db, dineway.configuredPlugins, id);
|
|
32
|
+
|
|
33
|
+
if (!result.success) return unwrapResult(result);
|
|
34
|
+
|
|
35
|
+
await dineway.setPluginStatus(id, "inactive");
|
|
36
|
+
await setCronTasksEnabled(dineway.db, id, false);
|
|
37
|
+
|
|
38
|
+
return unwrapResult(result);
|
|
39
|
+
};
|