leedstack 3.1.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/LICENSE +21 -0
- package/README.md +364 -0
- package/bin/create-stack.js +277 -0
- package/package.json +60 -0
- package/tools/templates/backend/go-echo/backend/.env.example +10 -0
- package/tools/templates/backend/go-echo/backend/cmd/server/main.go.ejs +57 -0
- package/tools/templates/backend/go-echo/backend/go.mod.ejs +10 -0
- package/tools/templates/backend/go-echo/backend/internal/handlers/example.go +15 -0
- package/tools/templates/backend/go-echo/backend/internal/handlers/health.go +13 -0
- package/tools/templates/backend/java-spring/backend/.env.example +10 -0
- package/tools/templates/backend/java-spring/backend/pom.xml.ejs +64 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/Application.java.ejs +11 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/config/SecurityConfig.java.ejs +64 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/controller/ExampleController.java +19 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/controller/HealthController.java +15 -0
- package/tools/templates/backend/java-spring/backend/src/main/resources/application.yml.ejs +20 -0
- package/tools/templates/backend/node-express/backend/.env.example +10 -0
- package/tools/templates/backend/node-express/backend/.eslintrc.json +21 -0
- package/tools/templates/backend/node-express/backend/.prettierrc +10 -0
- package/tools/templates/backend/node-express/backend/Dockerfile +52 -0
- package/tools/templates/backend/node-express/backend/README.md +68 -0
- package/tools/templates/backend/node-express/backend/package.json.ejs +37 -0
- package/tools/templates/backend/node-express/backend/src/index.ts.ejs +75 -0
- package/tools/templates/backend/node-express/backend/src/routes/health.ts +54 -0
- package/tools/templates/backend/node-express/backend/tsconfig.json +17 -0
- package/tools/templates/backend/python-fastapi/backend/.env.example +18 -0
- package/tools/templates/backend/python-fastapi/backend/README.md +73 -0
- package/tools/templates/backend/python-fastapi/backend/app/__init__.py +1 -0
- package/tools/templates/backend/python-fastapi/backend/app/main.py.ejs +68 -0
- package/tools/templates/backend/python-fastapi/backend/requirements.txt.ejs +22 -0
- package/tools/templates/base/.dockerignore +16 -0
- package/tools/templates/base/.env.example +31 -0
- package/tools/templates/base/.github/workflows/ci.yml.ejs +124 -0
- package/tools/templates/base/.github/workflows/deploy-separate.yml.example +144 -0
- package/tools/templates/base/.vscode/extensions.json +17 -0
- package/tools/templates/base/.vscode/settings.json +49 -0
- package/tools/templates/base/Makefile +98 -0
- package/tools/templates/base/README.md.ejs +118 -0
- package/tools/templates/base/docker-compose.yml.ejs +49 -0
- package/tools/templates/base/package.json.ejs +30 -0
- package/tools/templates/base/scripts/split-repos.sh +189 -0
- package/tools/templates/db/postgres/backend/java-spring/backend/pom.xml.ejs +81 -0
- package/tools/templates/db/postgres/backend/java-spring/backend/src/main/resources/application.yml.ejs +39 -0
- package/tools/templates/db/postgres/backend/java-spring/backend/src/main/resources/db/migration/V1__init.sql +17 -0
- package/tools/templates/db/postgres/backend/node-express/backend/.env.example +10 -0
- package/tools/templates/db/postgres/backend/node-express/backend/package.json.ejs +32 -0
- package/tools/templates/db/postgres/backend/node-express/backend/prisma/schema.prisma.ejs +39 -0
- package/tools/templates/frontend/angular/frontend/.env.example +9 -0
- package/tools/templates/frontend/angular/frontend/angular.json +66 -0
- package/tools/templates/frontend/angular/frontend/package.json.ejs +31 -0
- package/tools/templates/frontend/angular/frontend/src/app/app.component.ts +30 -0
- package/tools/templates/frontend/angular/frontend/src/app/app.routes.ts +18 -0
- package/tools/templates/frontend/angular/frontend/src/app/components/home.component.ts +24 -0
- package/tools/templates/frontend/angular/frontend/src/app/services/api.service.ts +48 -0
- package/tools/templates/frontend/angular/frontend/src/favicon.ico +1 -0
- package/tools/templates/frontend/angular/frontend/src/index.html +13 -0
- package/tools/templates/frontend/angular/frontend/src/main.ts +10 -0
- package/tools/templates/frontend/angular/frontend/src/styles.css +31 -0
- package/tools/templates/frontend/angular/frontend/tsconfig.app.json +9 -0
- package/tools/templates/frontend/angular/frontend/tsconfig.json +27 -0
- package/tools/templates/frontend/nextjs/frontend/.env.example +9 -0
- package/tools/templates/frontend/nextjs/frontend/next.config.js +37 -0
- package/tools/templates/frontend/nextjs/frontend/package.json.ejs +25 -0
- package/tools/templates/frontend/nextjs/frontend/src/app/globals.css +31 -0
- package/tools/templates/frontend/nextjs/frontend/src/app/layout.tsx +36 -0
- package/tools/templates/frontend/nextjs/frontend/src/app/page.tsx +19 -0
- package/tools/templates/frontend/nextjs/frontend/src/lib/api.ts +45 -0
- package/tools/templates/frontend/nextjs/frontend/tsconfig.json +27 -0
- package/tools/templates/frontend/react/frontend/.env.example +9 -0
- package/tools/templates/frontend/react/frontend/.eslintrc.json +32 -0
- package/tools/templates/frontend/react/frontend/.prettierrc +10 -0
- package/tools/templates/frontend/react/frontend/Dockerfile +37 -0
- package/tools/templates/frontend/react/frontend/README.md +54 -0
- package/tools/templates/frontend/react/frontend/index.html +13 -0
- package/tools/templates/frontend/react/frontend/nginx.conf +35 -0
- package/tools/templates/frontend/react/frontend/package.json.ejs +41 -0
- package/tools/templates/frontend/react/frontend/public/vite.svg +4 -0
- package/tools/templates/frontend/react/frontend/src/App.css +65 -0
- package/tools/templates/frontend/react/frontend/src/App.jsx +41 -0
- package/tools/templates/frontend/react/frontend/src/assets/react.svg +7 -0
- package/tools/templates/frontend/react/frontend/src/components/ErrorBoundary.jsx +62 -0
- package/tools/templates/frontend/react/frontend/src/components/Home.jsx +58 -0
- package/tools/templates/frontend/react/frontend/src/components/__tests__/Home.test.jsx +74 -0
- package/tools/templates/frontend/react/frontend/src/index.css +31 -0
- package/tools/templates/frontend/react/frontend/src/lib/api.js +42 -0
- package/tools/templates/frontend/react/frontend/src/lib/env.js +58 -0
- package/tools/templates/frontend/react/frontend/src/main.jsx +16 -0
- package/tools/templates/frontend/react/frontend/src/setupTests.js +8 -0
- package/tools/templates/frontend/react/frontend/vite.config.js +30 -0
- package/tools/templates/frontend/react/frontend/vitest.config.js +20 -0
- package/tools/templates/frontend/svelte/frontend/.env.example +9 -0
- package/tools/templates/frontend/svelte/frontend/package.json.ejs +21 -0
- package/tools/templates/frontend/svelte/frontend/src/app.html +12 -0
- package/tools/templates/frontend/svelte/frontend/src/lib/api.ts +45 -0
- package/tools/templates/frontend/svelte/frontend/src/routes/+layout.svelte +56 -0
- package/tools/templates/frontend/svelte/frontend/src/routes/+page.svelte +20 -0
- package/tools/templates/frontend/svelte/frontend/static/favicon.png +1 -0
- package/tools/templates/frontend/svelte/frontend/svelte.config.js +10 -0
- package/tools/templates/frontend/svelte/frontend/vite.config.js +9 -0
- package/tools/templates/frontend/vue/frontend/.env.example +9 -0
- package/tools/templates/frontend/vue/frontend/index.html +13 -0
- package/tools/templates/frontend/vue/frontend/package.json.ejs +20 -0
- package/tools/templates/frontend/vue/frontend/src/App.vue +60 -0
- package/tools/templates/frontend/vue/frontend/src/lib/api.js +42 -0
- package/tools/templates/frontend/vue/frontend/src/main.js +33 -0
- package/tools/templates/frontend/vue/frontend/src/views/ApiTest.vue +39 -0
- package/tools/templates/frontend/vue/frontend/src/views/Home.vue +30 -0
- package/tools/templates/frontend/vue/frontend/vite.config.js +9 -0
- package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/controller/AdminController.java +41 -0
- package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/entity/User.java +55 -0
- package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/repository/UserRepository.java +8 -0
- package/tools/templates/modules/admin/frontend/svelte/frontend/src/routes/dashboard/+page.svelte +93 -0
- package/tools/templates/modules/auth/backend/node-express/backend/src/middleware/auth.ts +42 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/hooks.client.ts +3 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/lib/auth.ts +104 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/routes/callback/+page.svelte +18 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/routes/login/+page.svelte +12 -0
- package/tools/templates/modules/chatbot/backend/node-express/backend/src/index.ts.ejs +69 -0
- package/tools/templates/modules/chatbot/backend/node-express/backend/src/routes/chatbot.ts.ejs +37 -0
- package/tools/templates/modules/chatbot/backend/node-express/backend/src/services/chatbotService.ts +124 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/main.py.ejs +69 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/routes/chatbot.py +38 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/services/chatbot_service.py +123 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/requirements.txt +1 -0
- package/tools/templates/modules/chatbot/frontend/react/frontend/src/App.jsx.ejs +74 -0
- package/tools/templates/modules/chatbot/frontend/react/frontend/src/components/Chatbot.css +198 -0
- package/tools/templates/modules/chatbot/frontend/react/frontend/src/components/Chatbot.jsx +113 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/controller/ContactController.java +29 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/entity/ContactMessage.java +66 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/repository/ContactMessageRepository.java +8 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/resources/db/migration/V2__contact.sql +7 -0
- package/tools/templates/modules/contact/frontend/svelte/frontend/src/routes/contact/+page.svelte +80 -0
- package/tools/templates/modules/payments/backend/java-spring/backend/src/main/java/com/app/controller/PaymentController.java +69 -0
- package/tools/templates/modules/payments/backend/node-express/backend/src/routes/payments.ts +30 -0
- package/tools/templates/modules/payments/backend/node-express/backend/src/routes/webhook.ts +36 -0
- package/tools/templates/modules/payments/frontend/svelte/frontend/src/lib/payments.ts +28 -0
package/tools/templates/modules/admin/frontend/svelte/frontend/src/routes/dashboard/+page.svelte
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { getToken, isAuthenticated } from '$lib/auth';
|
|
4
|
+
import { goto } from '$app/navigation';
|
|
5
|
+
|
|
6
|
+
let stats: any = null;
|
|
7
|
+
let loading = true;
|
|
8
|
+
let error = '';
|
|
9
|
+
|
|
10
|
+
onMount(async () => {
|
|
11
|
+
const auth = await new Promise<boolean>((resolve) => {
|
|
12
|
+
const unsubscribe = isAuthenticated.subscribe(value => {
|
|
13
|
+
resolve(value);
|
|
14
|
+
unsubscribe();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (!auth) {
|
|
19
|
+
goto('/login');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const API_BASE = import.meta.env.PUBLIC_API_BASE || 'http://localhost:8080';
|
|
25
|
+
const token = await getToken();
|
|
26
|
+
|
|
27
|
+
const response = await fetch(`${API_BASE}/api/admin/stats`, {
|
|
28
|
+
headers: {
|
|
29
|
+
'Authorization': `Bearer ${token}`
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (response.ok) {
|
|
34
|
+
stats = await response.json();
|
|
35
|
+
} else {
|
|
36
|
+
error = 'Failed to load stats';
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
error = 'Error loading stats';
|
|
40
|
+
} finally {
|
|
41
|
+
loading = false;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<h1>Admin Dashboard</h1>
|
|
47
|
+
|
|
48
|
+
{#if loading}
|
|
49
|
+
<p>Loading...</p>
|
|
50
|
+
{:else if error}
|
|
51
|
+
<p style="color: red;">{error}</p>
|
|
52
|
+
{:else if stats}
|
|
53
|
+
<div class="stats">
|
|
54
|
+
<div class="stat-card">
|
|
55
|
+
<h2>Users</h2>
|
|
56
|
+
<p class="stat-value">{stats.users}</p>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<% if (modules.contact) { -%>
|
|
60
|
+
<div class="stat-card">
|
|
61
|
+
<h2>Contact Messages</h2>
|
|
62
|
+
<p class="stat-value">{stats.contacts}</p>
|
|
63
|
+
</div>
|
|
64
|
+
<% } -%>
|
|
65
|
+
</div>
|
|
66
|
+
{/if}
|
|
67
|
+
|
|
68
|
+
<style>
|
|
69
|
+
.stats {
|
|
70
|
+
display: grid;
|
|
71
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
72
|
+
gap: 1rem;
|
|
73
|
+
margin-top: 2rem;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.stat-card {
|
|
77
|
+
padding: 1.5rem;
|
|
78
|
+
background: #f5f5f5;
|
|
79
|
+
border-radius: 8px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.stat-card h2 {
|
|
83
|
+
margin: 0 0 0.5rem 0;
|
|
84
|
+
font-size: 1rem;
|
|
85
|
+
color: #666;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.stat-value {
|
|
89
|
+
font-size: 2.5rem;
|
|
90
|
+
font-weight: bold;
|
|
91
|
+
margin: 0;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
3
|
+
|
|
4
|
+
const JWKS = createRemoteJWKSet(new URL(`https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`));
|
|
5
|
+
const ISSUER = `https://${process.env.AUTH0_DOMAIN}/`;
|
|
6
|
+
const AUDIENCE = process.env.AUTH0_AUDIENCE;
|
|
7
|
+
|
|
8
|
+
export async function authMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
9
|
+
const authHeader = req.headers.authorization;
|
|
10
|
+
|
|
11
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
12
|
+
return res.status(401).json({ error: 'Unauthorized' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const token = authHeader.substring(7);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const { payload } = await jwtVerify(token, JWKS, {
|
|
19
|
+
issuer: ISSUER,
|
|
20
|
+
audience: AUDIENCE
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
(req as any).user = payload;
|
|
24
|
+
next();
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error('JWT verification failed:', err);
|
|
27
|
+
return res.status(401).json({ error: 'Invalid token' });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function requireScope(scope: string) {
|
|
32
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
33
|
+
const user = (req as any).user;
|
|
34
|
+
const scopes = user?.scope?.split(' ') || [];
|
|
35
|
+
|
|
36
|
+
if (!scopes.includes(scope)) {
|
|
37
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
next();
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Auth0Client } from '@auth0/auth0-spa-js';
|
|
2
|
+
import { writable } from 'svelte/store';
|
|
3
|
+
|
|
4
|
+
const domain = import.meta.env.PUBLIC_AUTH0_DOMAIN || '';
|
|
5
|
+
const clientId = import.meta.env.PUBLIC_AUTH0_CLIENT_ID || '';
|
|
6
|
+
const audience = import.meta.env.PUBLIC_AUTH0_AUDIENCE || '';
|
|
7
|
+
|
|
8
|
+
let auth0Client: Auth0Client;
|
|
9
|
+
|
|
10
|
+
export const isAuthenticated = writable(false);
|
|
11
|
+
export const user = writable(null);
|
|
12
|
+
export const token = writable('');
|
|
13
|
+
|
|
14
|
+
async function createClient() {
|
|
15
|
+
auth0Client = new Auth0Client({
|
|
16
|
+
domain,
|
|
17
|
+
clientId,
|
|
18
|
+
authorizationParams: {
|
|
19
|
+
audience,
|
|
20
|
+
redirect_uri: window.location.origin + '/callback'
|
|
21
|
+
},
|
|
22
|
+
cacheLocation: 'localstorage'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return auth0Client;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function initAuth() {
|
|
29
|
+
if (!auth0Client) {
|
|
30
|
+
await createClient();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const isAuth = await auth0Client.isAuthenticated();
|
|
35
|
+
isAuthenticated.set(isAuth);
|
|
36
|
+
|
|
37
|
+
if (isAuth) {
|
|
38
|
+
const userInfo = await auth0Client.getUser();
|
|
39
|
+
user.set(userInfo);
|
|
40
|
+
|
|
41
|
+
const accessToken = await auth0Client.getTokenSilently();
|
|
42
|
+
token.set(accessToken);
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('Auth init error:', err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function login() {
|
|
50
|
+
if (!auth0Client) {
|
|
51
|
+
await createClient();
|
|
52
|
+
}
|
|
53
|
+
await auth0Client.loginWithRedirect();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function handleCallback() {
|
|
57
|
+
if (!auth0Client) {
|
|
58
|
+
await createClient();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await auth0Client.handleRedirectCallback();
|
|
63
|
+
const userInfo = await auth0Client.getUser();
|
|
64
|
+
const accessToken = await auth0Client.getTokenSilently();
|
|
65
|
+
|
|
66
|
+
isAuthenticated.set(true);
|
|
67
|
+
user.set(userInfo);
|
|
68
|
+
token.set(accessToken);
|
|
69
|
+
|
|
70
|
+
return true;
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error('Callback error:', err);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function logout() {
|
|
78
|
+
if (!auth0Client) {
|
|
79
|
+
await createClient();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await auth0Client.logout({
|
|
83
|
+
logoutParams: {
|
|
84
|
+
returnTo: window.location.origin
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
isAuthenticated.set(false);
|
|
89
|
+
user.set(null);
|
|
90
|
+
token.set('');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function getToken() {
|
|
94
|
+
if (!auth0Client) {
|
|
95
|
+
await createClient();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
return await auth0Client.getTokenSilently();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error('Get token error:', err);
|
|
102
|
+
return '';
|
|
103
|
+
}
|
|
104
|
+
}
|
package/tools/templates/modules/auth/frontend/svelte/frontend/src/routes/callback/+page.svelte
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { goto } from '$app/navigation';
|
|
4
|
+
import { handleCallback } from '$lib/auth';
|
|
5
|
+
|
|
6
|
+
onMount(async () => {
|
|
7
|
+
const success = await handleCallback();
|
|
8
|
+
if (success) {
|
|
9
|
+
goto('/');
|
|
10
|
+
} else {
|
|
11
|
+
goto('/login');
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div>
|
|
17
|
+
<p>Processing login...</p>
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import dotenv from 'dotenv';
|
|
4
|
+
<% if (modules.auth) { -%>
|
|
5
|
+
import authRoutes from './routes/auth';
|
|
6
|
+
<% } -%>
|
|
7
|
+
<% if (modules.payments) { -%>
|
|
8
|
+
import stripeRoutes from './routes/stripe';
|
|
9
|
+
<% } -%>
|
|
10
|
+
<% if (modules.contact) { -%>
|
|
11
|
+
import contactRoutes from './routes/contact';
|
|
12
|
+
<% } -%>
|
|
13
|
+
<% if (modules.admin) { -%>
|
|
14
|
+
import adminRoutes from './routes/admin';
|
|
15
|
+
<% } -%>
|
|
16
|
+
import chatbotRoutes from './routes/chatbot';
|
|
17
|
+
|
|
18
|
+
dotenv.config();
|
|
19
|
+
|
|
20
|
+
const app = express();
|
|
21
|
+
const PORT = process.env.PORT || 8080;
|
|
22
|
+
|
|
23
|
+
// CORS configuration - allow only the configured frontend URL
|
|
24
|
+
const allowedOrigins = [
|
|
25
|
+
process.env.FRONTEND_URL
|
|
26
|
+
].filter(Boolean);
|
|
27
|
+
|
|
28
|
+
app.use(cors({
|
|
29
|
+
origin: (origin, callback) => {
|
|
30
|
+
if (!origin) return callback(null, true);
|
|
31
|
+
if (allowedOrigins.indexOf(origin) !== -1) {
|
|
32
|
+
callback(null, true);
|
|
33
|
+
} else {
|
|
34
|
+
callback(new Error('Not allowed by CORS'));
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
credentials: true
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
app.use(express.json());
|
|
41
|
+
|
|
42
|
+
// Example route
|
|
43
|
+
app.get('/api/example', (req, res) => {
|
|
44
|
+
res.json({
|
|
45
|
+
message: 'Hello from node-express backend!',
|
|
46
|
+
timestamp: new Date().toISOString()
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Module routes
|
|
51
|
+
<% if (modules.auth) { -%>
|
|
52
|
+
app.use('/api/auth', authRoutes);
|
|
53
|
+
<% } -%>
|
|
54
|
+
<% if (modules.payments) { -%>
|
|
55
|
+
app.use('/stripe', stripeRoutes);
|
|
56
|
+
<% } -%>
|
|
57
|
+
<% if (modules.contact) { -%>
|
|
58
|
+
app.use('/api/contact', contactRoutes);
|
|
59
|
+
<% } -%>
|
|
60
|
+
<% if (modules.admin) { -%>
|
|
61
|
+
app.use('/api/admin', adminRoutes);
|
|
62
|
+
<% } -%>
|
|
63
|
+
app.use('/api/chatbot', chatbotRoutes);
|
|
64
|
+
|
|
65
|
+
app.listen(PORT, () => {
|
|
66
|
+
console.log(`✅ Server running on http://localhost:${PORT}`);
|
|
67
|
+
console.log(`📡 API endpoint: http://localhost:${PORT}/api/example`);
|
|
68
|
+
console.log(`💬 Chatbot endpoint: http://localhost:${PORT}/api/chatbot`);
|
|
69
|
+
});
|
package/tools/templates/modules/chatbot/backend/node-express/backend/src/routes/chatbot.ts.ejs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Router, Request, Response } from 'express';
|
|
2
|
+
import { ChatbotService } from '../services/chatbotService';
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
const chatbotService = new ChatbotService();
|
|
6
|
+
|
|
7
|
+
interface ChatMessage {
|
|
8
|
+
role: 'user' | 'assistant';
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ChatRequest {
|
|
13
|
+
message: string;
|
|
14
|
+
history?: ChatMessage[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
router.post('/', async (req: Request, res: Response) => {
|
|
18
|
+
try {
|
|
19
|
+
const { message, history = [] }: ChatRequest = req.body;
|
|
20
|
+
|
|
21
|
+
if (!message || typeof message !== 'string') {
|
|
22
|
+
return res.status(400).json({ error: 'Message is required' });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const response = await chatbotService.generateResponse(message, history);
|
|
26
|
+
|
|
27
|
+
res.json({ message: response });
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('Chatbot error:', error);
|
|
30
|
+
res.status(500).json({
|
|
31
|
+
error: 'Failed to generate response',
|
|
32
|
+
message: 'Sorry, I encountered an error. Please try again.'
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export default router;
|
package/tools/templates/modules/chatbot/backend/node-express/backend/src/services/chatbotService.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
interface ChatMessage {
|
|
2
|
+
role: 'user' | 'assistant';
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class ChatbotService {
|
|
7
|
+
private openaiApiKey: string | undefined;
|
|
8
|
+
private anthropicApiKey: string | undefined;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.openaiApiKey = process.env.OPENAI_API_KEY;
|
|
12
|
+
this.anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async generateResponse(message: string, history: ChatMessage[]): Promise<string> {
|
|
16
|
+
// Try LLM providers in order of preference
|
|
17
|
+
if (this.anthropicApiKey) {
|
|
18
|
+
return this.generateAnthropicResponse(message, history);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (this.openaiApiKey) {
|
|
22
|
+
return this.generateOpenAIResponse(message, history);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Fallback to basic rule-based responses
|
|
26
|
+
return this.generateBasicResponse(message);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private async generateOpenAIResponse(message: string, history: ChatMessage[]): Promise<string> {
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
'Authorization': `Bearer ${this.openaiApiKey}`
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
model: 'gpt-4o-mini',
|
|
39
|
+
messages: [
|
|
40
|
+
{ role: 'system', content: 'You are a helpful assistant for this web application. Be concise and friendly.' },
|
|
41
|
+
...history.map(msg => ({ role: msg.role, content: msg.content })),
|
|
42
|
+
{ role: 'user', content: message }
|
|
43
|
+
],
|
|
44
|
+
max_tokens: 500,
|
|
45
|
+
temperature: 0.7
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error(`OpenAI API error: ${response.statusText}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return data.choices[0]?.message?.content || this.generateBasicResponse(message);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('OpenAI error:', error);
|
|
57
|
+
return this.generateBasicResponse(message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private async generateAnthropicResponse(message: string, history: ChatMessage[]): Promise<string> {
|
|
62
|
+
try {
|
|
63
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
'x-api-key': this.anthropicApiKey!,
|
|
68
|
+
'anthropic-version': '2023-06-01'
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
model: 'claude-3-5-haiku-20241022',
|
|
72
|
+
max_tokens: 500,
|
|
73
|
+
system: 'You are a helpful assistant for this web application. Be concise and friendly.',
|
|
74
|
+
messages: [
|
|
75
|
+
...history.map(msg => ({ role: msg.role, content: msg.content })),
|
|
76
|
+
{ role: 'user', content: message }
|
|
77
|
+
]
|
|
78
|
+
})
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`Anthropic API error: ${response.statusText}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const data = await response.json();
|
|
86
|
+
return data.content[0]?.text || this.generateBasicResponse(message);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Anthropic error:', error);
|
|
89
|
+
return this.generateBasicResponse(message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private generateBasicResponse(message: string): string {
|
|
94
|
+
const lowerMessage = message.toLowerCase();
|
|
95
|
+
|
|
96
|
+
// Greeting patterns
|
|
97
|
+
if (/(hello|hi|hey|greetings)/i.test(lowerMessage)) {
|
|
98
|
+
return "Hello! I'm here to help. You can ask me questions about this application or configure me with an AI API key for more advanced responses.";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Help patterns
|
|
102
|
+
if (/(help|what can you do|how do you work)/i.test(lowerMessage)) {
|
|
103
|
+
return "I can answer questions and assist you with this application. Currently, I'm running in basic mode. To unlock AI-powered responses, add OPENAI_API_KEY or ANTHROPIC_API_KEY to your backend .env file.";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Feature questions
|
|
107
|
+
if (/(feature|what|how)/i.test(lowerMessage)) {
|
|
108
|
+
return "This is a full-stack web application with authentication, database integration, and more. Feel free to explore the codebase or ask me specific questions!";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// API/Configuration questions
|
|
112
|
+
if (/(api|configure|setup|install)/i.test(lowerMessage)) {
|
|
113
|
+
return "To configure AI responses:\n\n1. Get an API key from OpenAI or Anthropic\n2. Add it to backend/.env:\n OPENAI_API_KEY=sk-...\n or\n ANTHROPIC_API_KEY=sk-ant-...\n3. Restart the backend\n\nThen I'll be powered by AI!";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Thanks/Goodbye
|
|
117
|
+
if (/(thank|thanks|bye|goodbye)/i.test(lowerMessage)) {
|
|
118
|
+
return "You're welcome! Let me know if you need anything else.";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Default fallback
|
|
122
|
+
return "I received your message! I'm currently in basic mode with limited responses. For more intelligent conversations, configure an AI API key (OpenAI or Anthropic) in your backend environment variables.";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import os
|
|
5
|
+
<% if (modules.auth) { -%>
|
|
6
|
+
from app.routes import auth
|
|
7
|
+
<% } -%>
|
|
8
|
+
<% if (modules.payments) { -%>
|
|
9
|
+
from app.routes import stripe
|
|
10
|
+
<% } -%>
|
|
11
|
+
<% if (modules.contact) { -%>
|
|
12
|
+
from app.routes import contact
|
|
13
|
+
<% } -%>
|
|
14
|
+
<% if (modules.admin) { -%>
|
|
15
|
+
from app.routes import admin
|
|
16
|
+
<% } -%>
|
|
17
|
+
from app.routes import chatbot
|
|
18
|
+
|
|
19
|
+
app = FastAPI(
|
|
20
|
+
title="<%= AppName %> API",
|
|
21
|
+
description="API for <%= appName %>",
|
|
22
|
+
version="1.0.0"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# CORS configuration
|
|
26
|
+
allowed_origins = [
|
|
27
|
+
"http://localhost:5173", # Vite default
|
|
28
|
+
"http://localhost:3000", # Next.js default
|
|
29
|
+
os.getenv("FRONTEND_URL", "")
|
|
30
|
+
]
|
|
31
|
+
allowed_origins = [origin for origin in allowed_origins if origin]
|
|
32
|
+
|
|
33
|
+
app.add_middleware(
|
|
34
|
+
CORSMiddleware,
|
|
35
|
+
allow_origins=allowed_origins if os.getenv("NODE_ENV") != "development" else ["*"],
|
|
36
|
+
allow_credentials=True,
|
|
37
|
+
allow_methods=["*"],
|
|
38
|
+
allow_headers=["*"],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@app.get("/api/example")
|
|
42
|
+
async def example():
|
|
43
|
+
"""Example API endpoint"""
|
|
44
|
+
return {
|
|
45
|
+
"message": "Hello from python-fastapi backend!",
|
|
46
|
+
"timestamp": datetime.utcnow().isoformat()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Module routes
|
|
50
|
+
<% if (modules.auth) { -%>
|
|
51
|
+
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
|
|
52
|
+
<% } -%>
|
|
53
|
+
<% if (modules.payments) { -%>
|
|
54
|
+
app.include_router(stripe.router, prefix="/stripe", tags=["stripe"])
|
|
55
|
+
<% } -%>
|
|
56
|
+
<% if (modules.contact) { -%>
|
|
57
|
+
app.include_router(contact.router, prefix="/api/contact", tags=["contact"])
|
|
58
|
+
<% } -%>
|
|
59
|
+
<% if (modules.admin) { -%>
|
|
60
|
+
app.include_router(admin.router, prefix="/api/admin", tags=["admin"])
|
|
61
|
+
<% } -%>
|
|
62
|
+
app.include_router(chatbot.router, prefix="/api/chatbot", tags=["chatbot"])
|
|
63
|
+
|
|
64
|
+
@app.on_event("startup")
|
|
65
|
+
async def startup_event():
|
|
66
|
+
print("✅ Server running on http://localhost:8080")
|
|
67
|
+
print("📡 API endpoint: http://localhost:8080/api/example")
|
|
68
|
+
print("💬 Chatbot endpoint: http://localhost:8080/api/chatbot")
|
|
69
|
+
print("📚 API docs: http://localhost:8080/docs")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from app.services.chatbot_service import ChatbotService
|
|
5
|
+
|
|
6
|
+
router = APIRouter()
|
|
7
|
+
chatbot_service = ChatbotService()
|
|
8
|
+
|
|
9
|
+
class ChatMessage(BaseModel):
|
|
10
|
+
role: str
|
|
11
|
+
content: str
|
|
12
|
+
|
|
13
|
+
class ChatRequest(BaseModel):
|
|
14
|
+
message: str
|
|
15
|
+
history: Optional[List[ChatMessage]] = []
|
|
16
|
+
|
|
17
|
+
@router.post("")
|
|
18
|
+
async def chat(request: ChatRequest):
|
|
19
|
+
"""
|
|
20
|
+
Chat endpoint that works with or without AI API keys.
|
|
21
|
+
Falls back to basic responses if no API key is configured.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
if not request.message or not isinstance(request.message, str):
|
|
25
|
+
raise HTTPException(status_code=400, detail="Message is required")
|
|
26
|
+
|
|
27
|
+
response = await chatbot_service.generate_response(
|
|
28
|
+
request.message,
|
|
29
|
+
request.history
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return {"message": response}
|
|
33
|
+
except Exception as e:
|
|
34
|
+
print(f"Chatbot error: {e}")
|
|
35
|
+
raise HTTPException(
|
|
36
|
+
status_code=500,
|
|
37
|
+
detail="Sorry, I encountered an error. Please try again."
|
|
38
|
+
)
|