@cometchat/skills-cli 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/bin/cometchat.mjs +7 -0
- package/dist/index.js +4395 -0
- package/dist/registry/schemas/template.schema.json +142 -0
- package/dist/registry/v6/experiences/1-conversation-list/astro.template.json +89 -0
- package/dist/registry/v6/experiences/1-conversation-list/nextjs-pages.template.json +83 -0
- package/dist/registry/v6/experiences/1-conversation-list/nextjs.template.json +83 -0
- package/dist/registry/v6/experiences/1-conversation-list/react-router.template.json +102 -0
- package/dist/registry/v6/experiences/1-conversation-list/reactjs.template.json +86 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/astro/ChatApp.css.tpl +39 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/astro/ChatApp.tsx.tpl +86 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/astro/CometChatSelector.tsx.tpl +40 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/astro/chat.astro.tpl +17 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs/CometChatNoSSR.css.tpl +39 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs/CometChatNoSSR.tsx.tpl +86 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs/CometChatSelector.tsx.tpl +41 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs/chat-page.tsx.tpl +11 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs-pages/CometChatNoSSR.css.tpl +39 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs-pages/CometChatNoSSR.tsx.tpl +88 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs-pages/CometChatSelector.tsx.tpl +41 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs-pages/chat-page.tsx.tpl +15 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/react-router/CometChat-route.tsx.tpl +22 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/react-router/CometChatNoSSR.css.tpl +39 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/react-router/CometChatNoSSR.tsx.tpl +87 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/react-router/CometChatSelector.tsx.tpl +40 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/reactjs/App.css.tpl +32 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/reactjs/App.tsx.tpl +55 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/reactjs/CometChatSelector.tsx.tpl +40 -0
- package/dist/registry/v6/experiences/1-conversation-list/templates/reactjs/main.tsx.tpl +46 -0
- package/dist/registry/v6/experiences/2-one-to-one/astro.template.json +81 -0
- package/dist/registry/v6/experiences/2-one-to-one/nextjs.template.json +77 -0
- package/dist/registry/v6/experiences/2-one-to-one/react-router.template.json +93 -0
- package/dist/registry/v6/experiences/2-one-to-one/reactjs.template.json +77 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/astro/ChatApp.css.tpl +21 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/astro/ChatApp.tsx.tpl +66 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/astro/chat.astro.tpl +17 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/nextjs/CometChatNoSSR.css.tpl +21 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/nextjs/CometChatNoSSR.tsx.tpl +66 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/nextjs/chat-page.tsx.tpl +11 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/react-router/CometChat-route.tsx.tpl +21 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/react-router/CometChatNoSSR.css.tpl +21 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/react-router/CometChatNoSSR.tsx.tpl +67 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/reactjs/App.css.tpl +4 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/reactjs/App.tsx.tpl +44 -0
- package/dist/registry/v6/experiences/2-one-to-one/templates/reactjs/main.tsx.tpl +46 -0
- package/dist/registry/v6/experiences/3-tab-based/astro.template.json +93 -0
- package/dist/registry/v6/experiences/3-tab-based/nextjs.template.json +90 -0
- package/dist/registry/v6/experiences/3-tab-based/react-router.template.json +105 -0
- package/dist/registry/v6/experiences/3-tab-based/reactjs.template.json +90 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/astro/ChatApp.css.tpl +39 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/astro/ChatApp.tsx.tpl +93 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/astro/CometChatTabs.css.tpl +43 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/astro/CometChatTabs.tsx.tpl +72 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/astro/chat.astro.tpl +17 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/nextjs/CometChatNoSSR.css.tpl +39 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/nextjs/CometChatNoSSR.tsx.tpl +93 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/nextjs/CometChatTabs.css.tpl +43 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/nextjs/CometChatTabs.tsx.tpl +73 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/nextjs/chat-page.tsx.tpl +11 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/react-router/CometChat-route.tsx.tpl +21 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/react-router/CometChatNoSSR.css.tpl +39 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/react-router/CometChatNoSSR.tsx.tpl +94 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/react-router/CometChatTabs.css.tpl +43 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/react-router/CometChatTabs.tsx.tpl +72 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/reactjs/App.css.tpl +7 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/reactjs/App.tsx.tpl +62 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/reactjs/CometChatTabs.css.tpl +43 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/reactjs/CometChatTabs.tsx.tpl +72 -0
- package/dist/registry/v6/experiences/3-tab-based/templates/reactjs/main.tsx.tpl +46 -0
- package/dist/registry/v6/features/catalog.json +344 -0
- package/dist/registry/v6/production/astro/cometchat-token.ts.tpl +86 -0
- package/dist/registry/v6/production/astro.template.json +51 -0
- package/dist/registry/v6/production/nextjs/route.ts.tpl +79 -0
- package/dist/registry/v6/production/nextjs.template.json +50 -0
- package/dist/registry/v6/production/react-router/api.cometchat-token.ts.tpl +73 -0
- package/dist/registry/v6/production/react-router.template.json +58 -0
- package/dist/registry/v6/user-mgmt/astro/cometchat-user.ts.tpl +164 -0
- package/dist/registry/v6/user-mgmt/astro.template.json +33 -0
- package/dist/registry/v6/user-mgmt/nextjs/route.ts.tpl +212 -0
- package/dist/registry/v6/user-mgmt/nextjs.template.json +57 -0
- package/dist/registry/v6/user-mgmt/react-router/api.cometchat-user.ts.tpl +176 -0
- package/dist/registry/v6/user-mgmt/react-router.template.json +40 -0
- package/dist/registry/v6/widget/astro/CometChatWidget.css.tpl +105 -0
- package/dist/registry/v6/widget/astro/CometChatWidget.tsx.tpl +88 -0
- package/dist/registry/v6/widget/astro.template.json +38 -0
- package/dist/registry/v6/widget/nextjs/CometChatWidget.css.tpl +105 -0
- package/dist/registry/v6/widget/nextjs/CometChatWidget.tsx.tpl +88 -0
- package/dist/registry/v6/widget/nextjs.template.json +40 -0
- package/dist/registry/v6/widget/react-router/CometChatWidget.css.tpl +105 -0
- package/dist/registry/v6/widget/react-router/CometChatWidget.tsx.tpl +98 -0
- package/dist/registry/v6/widget/react-router.template.json +36 -0
- package/dist/registry/v6/widget/reactjs/CometChatWidget.css.tpl +105 -0
- package/dist/registry/v6/widget/reactjs/CometChatWidget.tsx.tpl +89 -0
- package/dist/registry/v6/widget/reactjs.template.json +36 -0
- package/package.json +53 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /api/cometchat-token — server-side endpoint that mints CometChat auth tokens.
|
|
3
|
+
*
|
|
4
|
+
* React Router v7 framework mode loader. The Auth Key NEVER leaves the
|
|
5
|
+
* server. The client calls this endpoint to get a short-lived auth token,
|
|
6
|
+
* then passes it to CometChatUIKit.loginWithAuthToken().
|
|
7
|
+
*
|
|
8
|
+
* Generated by `cometchat production-auth`. Customize freely:
|
|
9
|
+
* - Add real authentication: read the user's session cookie / JWT here
|
|
10
|
+
* - Map your app's user IDs to CometChat UIDs
|
|
11
|
+
* - Add rate limiting if exposed publicly
|
|
12
|
+
*
|
|
13
|
+
* Security note: this file MUST NOT use VITE_* env vars for the Auth Key.
|
|
14
|
+
* The whole point is to keep the Auth Key out of the browser.
|
|
15
|
+
*/
|
|
16
|
+
import type { LoaderFunctionArgs } from "react-router";
|
|
17
|
+
|
|
18
|
+
const COMETCHAT_APP_ID = process.env.VITE_COMETCHAT_APP_ID!;
|
|
19
|
+
const COMETCHAT_REGION = process.env.VITE_COMETCHAT_REGION!;
|
|
20
|
+
// IMPORTANT: server-only — no VITE_ prefix
|
|
21
|
+
const COMETCHAT_AUTH_KEY = process.env.COMETCHAT_AUTH_KEY!;
|
|
22
|
+
|
|
23
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
24
|
+
const url = new URL(request.url);
|
|
25
|
+
// ⚠ Replace this with your real auth: read the session, extract the user ID,
|
|
26
|
+
// then map it to a CometChat UID. Do not trust the query param in production.
|
|
27
|
+
const uid = url.searchParams.get("uid") ?? "cometchat-uid-1";
|
|
28
|
+
|
|
29
|
+
if (!COMETCHAT_APP_ID || !COMETCHAT_REGION || !COMETCHAT_AUTH_KEY) {
|
|
30
|
+
return Response.json(
|
|
31
|
+
{ error: "CometChat credentials not configured on the server" },
|
|
32
|
+
{ status: 500 },
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(
|
|
38
|
+
`https://${COMETCHAT_APP_ID}.api-${COMETCHAT_REGION}.cometchat.io/v3/users/${encodeURIComponent(uid)}/auth_tokens`,
|
|
39
|
+
{
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: {
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
appid: COMETCHAT_APP_ID,
|
|
44
|
+
apikey: COMETCHAT_AUTH_KEY,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
const text = await res.text();
|
|
51
|
+
return Response.json(
|
|
52
|
+
{ error: `CometChat token mint failed (${res.status})`, detail: text },
|
|
53
|
+
{ status: 502 },
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const body = (await res.json()) as { data?: { authToken?: string } };
|
|
58
|
+
const authToken = body.data?.authToken;
|
|
59
|
+
if (!authToken) {
|
|
60
|
+
return Response.json(
|
|
61
|
+
{ error: "CometChat returned no authToken in response" },
|
|
62
|
+
{ status: 502 },
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return Response.json({ authToken, uid });
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return Response.json(
|
|
69
|
+
{ error: "Token mint failed", detail: err instanceof Error ? err.message : String(err) },
|
|
70
|
+
{ status: 500 },
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../schemas/template.schema.json",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"experience_id": 1,
|
|
5
|
+
"framework": "react-router",
|
|
6
|
+
"sdk_compatibility": ">=6.0.0 <7.0.0",
|
|
7
|
+
"default_placement": "dedicated-route",
|
|
8
|
+
"deps": [],
|
|
9
|
+
"env_vars": [
|
|
10
|
+
{
|
|
11
|
+
"name": "COMETCHAT_AUTH_KEY",
|
|
12
|
+
"required": true,
|
|
13
|
+
"secret": true,
|
|
14
|
+
"description": "Server-side only Auth Key (no VITE_ prefix). Used by the /api/cometchat-token loader to mint user-scoped auth tokens. Remove VITE_COMETCHAT_AUTH_KEY from .env — that variable is for development only and exposes the key to the browser."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "VITE_COMETCHAT_APP_ID",
|
|
18
|
+
"required": true,
|
|
19
|
+
"description": "Same as before — App ID is safe to expose"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "VITE_COMETCHAT_REGION",
|
|
23
|
+
"required": true,
|
|
24
|
+
"description": "Same as before — region is safe to expose"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
{
|
|
29
|
+
"path": "app/routes/api.cometchat-token.ts",
|
|
30
|
+
"action": "create",
|
|
31
|
+
"template_ref": "api.cometchat-token.ts.tpl",
|
|
32
|
+
"owned": true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": "app/routes.ts",
|
|
36
|
+
"action": "patch",
|
|
37
|
+
"patch_mode": "insert-before",
|
|
38
|
+
"anchor": "] satisfies RouteConfig",
|
|
39
|
+
"patch_content": " route(\"api/cometchat-token\", \"routes/api.cometchat-token.ts\"),\n",
|
|
40
|
+
"skip_if_exists": "routes/api.cometchat-token.ts"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"verify_rules": [],
|
|
44
|
+
"next_steps": [
|
|
45
|
+
"1. Add COMETCHAT_AUTH_KEY (NO VITE_ prefix) to .env with your CometChat Auth Key.",
|
|
46
|
+
"2. REMOVE VITE_COMETCHAT_AUTH_KEY from .env (keep APP_ID and REGION).",
|
|
47
|
+
"3. In app/cometchat/CometChatNoSSR.tsx, replace the login flow:",
|
|
48
|
+
" FROM: CometChatUIKit.login('cometchat-uid-1').then(setUser).catch(setError);",
|
|
49
|
+
" TO: fetch('/api/cometchat-token?uid=' + encodeURIComponent(userId))",
|
|
50
|
+
" .then(r => r.json())",
|
|
51
|
+
" .then(data => CometChatUIKit.loginWithAuthToken(data.authToken))",
|
|
52
|
+
" .then(setUser)",
|
|
53
|
+
" .catch(e => setError(String(e)));",
|
|
54
|
+
"4. Replace 'cometchat-uid-1' with your real authenticated user's ID.",
|
|
55
|
+
"5. Restart the dev server (`npm run dev`) so the new env var takes effect.",
|
|
56
|
+
"6. The Auth Key is now ONLY in server memory — never sent to the browser."
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /api/cometchat-user — server-side CometChat user management.
|
|
3
|
+
*
|
|
4
|
+
* Astro API route. Handles POST (create), PUT (update), DELETE.
|
|
5
|
+
* The Auth Key NEVER leaves the server.
|
|
6
|
+
*
|
|
7
|
+
* Requires astro.config.mjs to have `output: 'server'` or `'hybrid'`.
|
|
8
|
+
*
|
|
9
|
+
* Generated by `cometchat add-user-mgmt`.
|
|
10
|
+
*
|
|
11
|
+
* SECURITY: this template is unauthenticated by design — add your own
|
|
12
|
+
* session check before exposing it publicly.
|
|
13
|
+
*/
|
|
14
|
+
import type { APIRoute } from "astro";
|
|
15
|
+
|
|
16
|
+
const COMETCHAT_APP_ID = import.meta.env.PUBLIC_COMETCHAT_APP_ID!;
|
|
17
|
+
const COMETCHAT_REGION = import.meta.env.PUBLIC_COMETCHAT_REGION!;
|
|
18
|
+
const COMETCHAT_AUTH_KEY = import.meta.env.COMETCHAT_AUTH_KEY!;
|
|
19
|
+
|
|
20
|
+
const COMETCHAT_API_BASE = `https://${COMETCHAT_APP_ID}.api-${COMETCHAT_REGION}.cometchat.io/v3`;
|
|
21
|
+
|
|
22
|
+
const baseHeaders = {
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
appid: COMETCHAT_APP_ID,
|
|
25
|
+
apikey: COMETCHAT_AUTH_KEY,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function json(data: unknown, status = 200) {
|
|
29
|
+
return new Response(JSON.stringify(data), {
|
|
30
|
+
status,
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function validateUid(uid: unknown): string | null {
|
|
36
|
+
if (typeof uid !== "string" || uid.length === 0) return "uid is required";
|
|
37
|
+
if (uid.length > 100) return "uid must be ≤100 characters";
|
|
38
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(uid)) {
|
|
39
|
+
return "uid must contain only alphanumeric characters, hyphens, and underscores";
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function configError() {
|
|
45
|
+
if (!COMETCHAT_APP_ID || !COMETCHAT_REGION || !COMETCHAT_AUTH_KEY) {
|
|
46
|
+
return json({ error: "CometChat credentials not configured on the server" }, 500);
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const POST: APIRoute = async ({ request }) => {
|
|
52
|
+
const cfgErr = configError();
|
|
53
|
+
if (cfgErr) return cfgErr;
|
|
54
|
+
|
|
55
|
+
let body;
|
|
56
|
+
try {
|
|
57
|
+
body = await request.json();
|
|
58
|
+
} catch {
|
|
59
|
+
return json({ error: "Invalid JSON body" }, 400);
|
|
60
|
+
}
|
|
61
|
+
const uidError = validateUid(body.uid);
|
|
62
|
+
if (uidError) return json({ error: uidError }, 400);
|
|
63
|
+
if (!body.name) return json({ error: "name is required" }, 400);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch(`${COMETCHAT_API_BASE}/users`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: baseHeaders,
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
uid: body.uid,
|
|
71
|
+
name: body.name,
|
|
72
|
+
...(body.avatar && { avatar: body.avatar }),
|
|
73
|
+
...(body.metadata && { metadata: body.metadata }),
|
|
74
|
+
...(body.tags && { tags: body.tags }),
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
const detail = await res.text();
|
|
79
|
+
return json(
|
|
80
|
+
{ error: `CometChat user create failed (${res.status})`, detail },
|
|
81
|
+
res.status === 409 ? 409 : 502,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const data = await res.json();
|
|
85
|
+
return json({ user: data.data }, 201);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
return json(
|
|
88
|
+
{ error: "Create failed", detail: err instanceof Error ? err.message : String(err) },
|
|
89
|
+
500,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const PUT: APIRoute = async ({ request }) => {
|
|
95
|
+
const cfgErr = configError();
|
|
96
|
+
if (cfgErr) return cfgErr;
|
|
97
|
+
|
|
98
|
+
let body;
|
|
99
|
+
try {
|
|
100
|
+
body = await request.json();
|
|
101
|
+
} catch {
|
|
102
|
+
return json({ error: "Invalid JSON body" }, 400);
|
|
103
|
+
}
|
|
104
|
+
const uidError = validateUid(body.uid);
|
|
105
|
+
if (uidError) return json({ error: uidError }, 400);
|
|
106
|
+
|
|
107
|
+
const updates: Record<string, unknown> = {};
|
|
108
|
+
if (body.name !== undefined) updates.name = body.name;
|
|
109
|
+
if (body.avatar !== undefined) updates.avatar = body.avatar;
|
|
110
|
+
if (body.metadata !== undefined) updates.metadata = body.metadata;
|
|
111
|
+
if (body.tags !== undefined) updates.tags = body.tags;
|
|
112
|
+
if (Object.keys(updates).length === 0) {
|
|
113
|
+
return json({ error: "No fields to update" }, 400);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const res = await fetch(`${COMETCHAT_API_BASE}/users/${encodeURIComponent(body.uid)}`, {
|
|
118
|
+
method: "PUT",
|
|
119
|
+
headers: baseHeaders,
|
|
120
|
+
body: JSON.stringify(updates),
|
|
121
|
+
});
|
|
122
|
+
if (!res.ok) {
|
|
123
|
+
const detail = await res.text();
|
|
124
|
+
return json({ error: `CometChat user update failed (${res.status})`, detail }, 502);
|
|
125
|
+
}
|
|
126
|
+
const data = await res.json();
|
|
127
|
+
return json({ user: data.data });
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return json(
|
|
130
|
+
{ error: "Update failed", detail: err instanceof Error ? err.message : String(err) },
|
|
131
|
+
500,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const DELETE: APIRoute = async ({ request }) => {
|
|
137
|
+
const cfgErr = configError();
|
|
138
|
+
if (cfgErr) return cfgErr;
|
|
139
|
+
|
|
140
|
+
const url = new URL(request.url);
|
|
141
|
+
const uid = url.searchParams.get("uid");
|
|
142
|
+
const uidError = validateUid(uid);
|
|
143
|
+
if (uidError) return json({ error: uidError }, 400);
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch(`${COMETCHAT_API_BASE}/users/${encodeURIComponent(uid as string)}`, {
|
|
147
|
+
method: "DELETE",
|
|
148
|
+
headers: baseHeaders,
|
|
149
|
+
body: JSON.stringify({ permanent: true }),
|
|
150
|
+
});
|
|
151
|
+
if (!res.ok) {
|
|
152
|
+
const detail = await res.text();
|
|
153
|
+
return json({ error: `CometChat user delete failed (${res.status})`, detail }, 502);
|
|
154
|
+
}
|
|
155
|
+
return json({ deleted: uid });
|
|
156
|
+
} catch (err) {
|
|
157
|
+
return json(
|
|
158
|
+
{ error: "Delete failed", detail: err instanceof Error ? err.message : String(err) },
|
|
159
|
+
500,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const prerender = false;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../schemas/template.schema.json",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"experience_id": 1,
|
|
5
|
+
"framework": "astro",
|
|
6
|
+
"sdk_compatibility": ">=6.0.0 <7.0.0",
|
|
7
|
+
"default_placement": "dedicated-route",
|
|
8
|
+
"deps": [],
|
|
9
|
+
"env_vars": [
|
|
10
|
+
{
|
|
11
|
+
"name": "COMETCHAT_AUTH_KEY",
|
|
12
|
+
"required": true,
|
|
13
|
+
"secret": true,
|
|
14
|
+
"description": "Server-side only Auth Key (no PUBLIC_ prefix)."
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"files": [
|
|
18
|
+
{
|
|
19
|
+
"path": "src/pages/api/cometchat-user.ts",
|
|
20
|
+
"action": "create",
|
|
21
|
+
"template_ref": "cometchat-user.ts.tpl",
|
|
22
|
+
"owned": true
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"verify_rules": [],
|
|
26
|
+
"next_steps": [
|
|
27
|
+
"1. Make sure your astro.config.mjs has output: 'server' or 'hybrid'.",
|
|
28
|
+
"2. Endpoint at /api/cometchat-user supports POST (create), PUT (update), DELETE.",
|
|
29
|
+
"3. Add COMETCHAT_AUTH_KEY (no PUBLIC_ prefix) to .env.",
|
|
30
|
+
"4. Wire it into your sign-up flow when an app user is created.",
|
|
31
|
+
"5. SECURITY: this template is unauthenticated. Add session checks before deploying."
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /api/cometchat-user — server-side CometChat user management.
|
|
3
|
+
*
|
|
4
|
+
* Handles three operations:
|
|
5
|
+
* POST — create a new CometChat user (call when an app user signs up)
|
|
6
|
+
* PATCH — update an existing user (call when profile changes)
|
|
7
|
+
* DELETE — delete a user (call when app user is deleted)
|
|
8
|
+
*
|
|
9
|
+
* All operations use the CometChat REST API with the server-only AUTH_KEY.
|
|
10
|
+
* The auth key NEVER leaves the server.
|
|
11
|
+
*
|
|
12
|
+
* Generated by `cometchat add-user-mgmt`.
|
|
13
|
+
*
|
|
14
|
+
* SECURITY: this template is unauthenticated by design — add your own
|
|
15
|
+
* session check (NextAuth, Clerk, custom JWT) before exposing it publicly.
|
|
16
|
+
* Otherwise anyone with the URL can create/delete users in your app.
|
|
17
|
+
*/
|
|
18
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
19
|
+
|
|
20
|
+
const COMETCHAT_APP_ID = process.env.NEXT_PUBLIC_COMETCHAT_APP_ID!;
|
|
21
|
+
const COMETCHAT_REGION = process.env.NEXT_PUBLIC_COMETCHAT_REGION!;
|
|
22
|
+
const COMETCHAT_AUTH_KEY = process.env.COMETCHAT_AUTH_KEY!;
|
|
23
|
+
|
|
24
|
+
interface CreateUserBody {
|
|
25
|
+
uid: string;
|
|
26
|
+
name: string;
|
|
27
|
+
avatar?: string;
|
|
28
|
+
link?: string;
|
|
29
|
+
role?: string;
|
|
30
|
+
metadata?: Record<string, unknown>;
|
|
31
|
+
tags?: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface UpdateUserBody {
|
|
35
|
+
uid: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
avatar?: string;
|
|
38
|
+
link?: string;
|
|
39
|
+
role?: string;
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
tags?: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const COMETCHAT_API_BASE = `https://${COMETCHAT_APP_ID}.api-${COMETCHAT_REGION}.cometchat.io/v3`;
|
|
45
|
+
|
|
46
|
+
const baseHeaders = {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
appid: COMETCHAT_APP_ID,
|
|
49
|
+
apikey: COMETCHAT_AUTH_KEY,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function validateConfig(): NextResponse | null {
|
|
53
|
+
if (!COMETCHAT_APP_ID || !COMETCHAT_REGION || !COMETCHAT_AUTH_KEY) {
|
|
54
|
+
return NextResponse.json(
|
|
55
|
+
{ error: "CometChat credentials not configured on the server" },
|
|
56
|
+
{ status: 500 },
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// CometChat UIDs must be alphanumeric + hyphens + underscores. Validate before
|
|
63
|
+
// passing to the API to give better error messages than the upstream 400.
|
|
64
|
+
function validateUid(uid: unknown): string | null {
|
|
65
|
+
if (typeof uid !== "string" || uid.length === 0) return "uid is required";
|
|
66
|
+
if (uid.length > 100) return "uid must be ≤100 characters";
|
|
67
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(uid)) {
|
|
68
|
+
return "uid must contain only alphanumeric characters, hyphens, and underscores";
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** POST — create a new user. Body: { uid, name, ...optional }. */
|
|
74
|
+
export async function POST(request: NextRequest) {
|
|
75
|
+
const configError = validateConfig();
|
|
76
|
+
if (configError) return configError;
|
|
77
|
+
|
|
78
|
+
let body: CreateUserBody;
|
|
79
|
+
try {
|
|
80
|
+
body = await request.json();
|
|
81
|
+
} catch {
|
|
82
|
+
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const uidError = validateUid(body.uid);
|
|
86
|
+
if (uidError) return NextResponse.json({ error: uidError }, { status: 400 });
|
|
87
|
+
if (!body.name || typeof body.name !== "string") {
|
|
88
|
+
return NextResponse.json({ error: "name is required" }, { status: 400 });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch(`${COMETCHAT_API_BASE}/users`, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: baseHeaders,
|
|
95
|
+
body: JSON.stringify({
|
|
96
|
+
uid: body.uid,
|
|
97
|
+
name: body.name,
|
|
98
|
+
...(body.avatar && { avatar: body.avatar }),
|
|
99
|
+
...(body.link && { link: body.link }),
|
|
100
|
+
...(body.role && { role: body.role }),
|
|
101
|
+
...(body.metadata && { metadata: body.metadata }),
|
|
102
|
+
...(body.tags && { tags: body.tags }),
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const detail = await res.text();
|
|
108
|
+
return NextResponse.json(
|
|
109
|
+
{ error: `CometChat user create failed (${res.status})`, detail },
|
|
110
|
+
{ status: res.status === 409 ? 409 : 502 },
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const data = (await res.json()) as { data?: unknown };
|
|
115
|
+
return NextResponse.json({ user: data.data }, { status: 201 });
|
|
116
|
+
} catch (err) {
|
|
117
|
+
return NextResponse.json(
|
|
118
|
+
{ error: "Create failed", detail: err instanceof Error ? err.message : String(err) },
|
|
119
|
+
{ status: 500 },
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** PATCH — update an existing user. Body: { uid, ...fields-to-update }. */
|
|
125
|
+
export async function PATCH(request: NextRequest) {
|
|
126
|
+
const configError = validateConfig();
|
|
127
|
+
if (configError) return configError;
|
|
128
|
+
|
|
129
|
+
let body: UpdateUserBody;
|
|
130
|
+
try {
|
|
131
|
+
body = await request.json();
|
|
132
|
+
} catch {
|
|
133
|
+
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const uidError = validateUid(body.uid);
|
|
137
|
+
if (uidError) return NextResponse.json({ error: uidError }, { status: 400 });
|
|
138
|
+
|
|
139
|
+
const updates: Record<string, unknown> = {};
|
|
140
|
+
if (body.name !== undefined) updates.name = body.name;
|
|
141
|
+
if (body.avatar !== undefined) updates.avatar = body.avatar;
|
|
142
|
+
if (body.link !== undefined) updates.link = body.link;
|
|
143
|
+
if (body.role !== undefined) updates.role = body.role;
|
|
144
|
+
if (body.metadata !== undefined) updates.metadata = body.metadata;
|
|
145
|
+
if (body.tags !== undefined) updates.tags = body.tags;
|
|
146
|
+
|
|
147
|
+
if (Object.keys(updates).length === 0) {
|
|
148
|
+
return NextResponse.json({ error: "No fields to update" }, { status: 400 });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const res = await fetch(`${COMETCHAT_API_BASE}/users/${encodeURIComponent(body.uid)}`, {
|
|
153
|
+
method: "PUT",
|
|
154
|
+
headers: baseHeaders,
|
|
155
|
+
body: JSON.stringify(updates),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
const detail = await res.text();
|
|
160
|
+
return NextResponse.json(
|
|
161
|
+
{ error: `CometChat user update failed (${res.status})`, detail },
|
|
162
|
+
{ status: 502 },
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const data = (await res.json()) as { data?: unknown };
|
|
167
|
+
return NextResponse.json({ user: data.data });
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return NextResponse.json(
|
|
170
|
+
{ error: "Update failed", detail: err instanceof Error ? err.message : String(err) },
|
|
171
|
+
{ status: 500 },
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** DELETE — delete a user. Query: ?uid=<uid>. */
|
|
177
|
+
export async function DELETE(request: NextRequest) {
|
|
178
|
+
const configError = validateConfig();
|
|
179
|
+
if (configError) return configError;
|
|
180
|
+
|
|
181
|
+
const uid = request.nextUrl.searchParams.get("uid");
|
|
182
|
+
const uidError = validateUid(uid);
|
|
183
|
+
if (uidError) return NextResponse.json({ error: uidError }, { status: 400 });
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const res = await fetch(
|
|
187
|
+
`${COMETCHAT_API_BASE}/users/${encodeURIComponent(uid as string)}`,
|
|
188
|
+
{
|
|
189
|
+
method: "DELETE",
|
|
190
|
+
headers: baseHeaders,
|
|
191
|
+
body: JSON.stringify({ permanent: true }),
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (!res.ok) {
|
|
196
|
+
const detail = await res.text();
|
|
197
|
+
return NextResponse.json(
|
|
198
|
+
{ error: `CometChat user delete failed (${res.status})`, detail },
|
|
199
|
+
{ status: 502 },
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return NextResponse.json({ deleted: uid });
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return NextResponse.json(
|
|
206
|
+
{ error: "Delete failed", detail: err instanceof Error ? err.message : String(err) },
|
|
207
|
+
{ status: 500 },
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const dynamic = "force-dynamic";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../schemas/template.schema.json",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"experience_id": 1,
|
|
5
|
+
"framework": "nextjs",
|
|
6
|
+
"sdk_compatibility": ">=6.0.0 <7.0.0",
|
|
7
|
+
"default_placement": "dedicated-route",
|
|
8
|
+
"deps": [],
|
|
9
|
+
"env_vars": [
|
|
10
|
+
{
|
|
11
|
+
"name": "COMETCHAT_AUTH_KEY",
|
|
12
|
+
"required": true,
|
|
13
|
+
"secret": true,
|
|
14
|
+
"description": "Server-side only Auth Key (no NEXT_PUBLIC_ prefix). Used by both /api/cometchat-token AND /api/cometchat-user."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "NEXT_PUBLIC_COMETCHAT_APP_ID",
|
|
18
|
+
"required": true
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "NEXT_PUBLIC_COMETCHAT_REGION",
|
|
22
|
+
"required": true
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"files": [
|
|
26
|
+
{
|
|
27
|
+
"path": "src/app/api/cometchat-user/route.ts",
|
|
28
|
+
"action": "create",
|
|
29
|
+
"template_ref": "route.ts.tpl",
|
|
30
|
+
"owned": true
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"verify_rules": [],
|
|
34
|
+
"next_steps": [
|
|
35
|
+
"1. The endpoint at /api/cometchat-user supports POST (create), PATCH (update), and DELETE.",
|
|
36
|
+
"2. Wire it into your sign-up flow. When a new user signs up, call POST /api/cometchat-user with { uid, name, avatar?, metadata? } to create their CometChat counterpart.",
|
|
37
|
+
"",
|
|
38
|
+
" Example with NextAuth (in your sign-up callback):",
|
|
39
|
+
" const res = await fetch('/api/cometchat-user', {",
|
|
40
|
+
" method: 'POST',",
|
|
41
|
+
" headers: { 'Content-Type': 'application/json' },",
|
|
42
|
+
" body: JSON.stringify({ uid: session.user.id, name: session.user.name }),",
|
|
43
|
+
" });",
|
|
44
|
+
"",
|
|
45
|
+
" Example with Clerk (in a Clerk webhook handler):",
|
|
46
|
+
" export async function POST(req: Request) {",
|
|
47
|
+
" const event = await req.json();",
|
|
48
|
+
" if (event.type === 'user.created') {",
|
|
49
|
+
" await fetch(`${process.env.NEXTAUTH_URL}/api/cometchat-user`, { ... });",
|
|
50
|
+
" }",
|
|
51
|
+
" }",
|
|
52
|
+
"",
|
|
53
|
+
"3. The endpoint validates uid is alphanumeric + hyphens only (CometChat constraint).",
|
|
54
|
+
"4. Errors are returned as { error, detail } with HTTP status codes.",
|
|
55
|
+
"5. SECURITY: in production, this endpoint should be authenticated. The template uses no auth — add session checks before deploying."
|
|
56
|
+
]
|
|
57
|
+
}
|