@shivasankaran18/stackd 2.0.2 → 13.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 +12 -7
- package/apps/cli/src/cli.ts +2 -28
- package/apps/cli/src/commands/create.ts +0 -30
- package/apps/cli/src/generators/auth/jwt.js +83 -0
- package/apps/cli/src/generators/auth/nextAuth.js +146 -0
- package/apps/cli/src/generators/auth/passport.js +234 -0
- package/apps/cli/src/generators/backend/django.js +30 -0
- package/apps/cli/src/generators/backend/expressjs.js +72 -0
- package/apps/cli/src/generators/backend/expressts.js +95 -0
- package/apps/cli/src/generators/frontend/angularts.js +29 -0
- package/apps/cli/src/generators/frontend/reactjs.js +42 -0
- package/apps/cli/src/generators/frontend/reactts.d.ts +13 -0
- package/apps/cli/src/generators/frontend/reactts.js +40 -0
- package/apps/cli/src/generators/frontend/vuejs.js +43 -0
- package/apps/cli/src/generators/frontend/vuets.js +53 -0
- package/apps/cli/src/generators/orm/drizzleSetup.js +102 -0
- package/apps/cli/src/generators/orm/mongoSetup.js +68 -0
- package/apps/cli/src/generators/orm/prismaSetup.js +14 -0
- package/apps/cli/src/generators/ui/shadcn.js +228 -0
- package/apps/cli/src/generators/ui/tailwindcss.js +126 -0
- package/apps/cli/src/scripts/backend/expressts.ts +1 -1
- package/apps/cli/src/scripts/frontend/angularts.ts +4 -2
- package/apps/cli/src/scripts/frontend/reactjs.ts +3 -2
- package/apps/cli/src/scripts/frontend/reactts.ts +3 -2
- package/apps/cli/src/scripts/orms/prismaSetup.ts +2 -2
- package/apps/cli/src/scripts/ui/shadcn.ts +10 -4
- package/apps/web/app/api/scaffold/route.ts +7 -7
- package/apps/web/app/scaffold/page.tsx +1 -1
- package/apps/web/package.json +2 -1
- package/apps/web/tsconfig.json +23 -125
- package/package.json +3 -33
- package/packages/typescript-config/base.json +19 -0
- package/packages/typescript-config/nextjs.json +12 -0
- package/packages/typescript-config/package.json +9 -0
- package/packages/typescript-config/react-library.json +7 -0
- package/packages/ui/eslint.config.mjs +4 -0
- package/packages/ui/package.json +27 -0
- package/packages/ui/src/button.tsx +20 -0
- package/packages/ui/src/card.tsx +27 -0
- package/packages/ui/src/code.tsx +11 -0
- package/packages/ui/tsconfig.json +8 -0
- package/packages/ui/turbo/generators/config.ts +30 -0
- package/packages/ui/turbo/generators/templates/component.hbs +8 -0
- package/stackd.ts +16 -6
- package/apps/cli/src/scripts/ui/tailwind.ts +0 -39
package/README.md
CHANGED
@@ -14,7 +14,7 @@ STACKD (Abbreviation TBD) is an open-source scaffolding tool that simplifies the
|
|
14
14
|
|
15
15
|
## 💡 Motivation
|
16
16
|
|
17
|
-
|
17
|
+
We got the motive to build this application because every time me and my team went for a hackathon, it used to take a lot of time to set up the project with basic things like frontend, UI setup, backend, ORM connection, authentication, and other configurations. People in the work environment also struggle with this, so we came up with this idea. STACKD allows programmers to jump directly into coding instead of spending time on tedious setup tasks.
|
18
18
|
|
19
19
|
## 🚀 Features
|
20
20
|
|
@@ -74,7 +74,7 @@ https://github.com/user-attachments/assets/465451f2-1e1a-48b6-a99e-253ef4f28e2d
|
|
74
74
|
- NextAuth
|
75
75
|
- Passport
|
76
76
|
|
77
|
-
|
77
|
+
## 📦 Installation
|
78
78
|
|
79
79
|
### Prerequisites
|
80
80
|
|
@@ -83,17 +83,22 @@ Make sure you have the following installed on your system:
|
|
83
83
|
- Node.js
|
84
84
|
- Git
|
85
85
|
|
86
|
-
###
|
86
|
+
### CLI Tool
|
87
|
+
|
88
|
+
To scaffold a new full-stack project, run:
|
87
89
|
|
88
90
|
```sh
|
89
|
-
npm
|
90
|
-
npx stackd create
|
91
|
+
npm create @shivasankaran18/stackd new proj-name
|
91
92
|
```
|
92
93
|
|
93
|
-
|
94
|
+
This will prompt you with setup questions and generate the project with the selected configurations.
|
95
|
+
|
96
|
+
### Web Tool
|
97
|
+
|
98
|
+
To launch the web-based setup tool locally, use:
|
94
99
|
|
95
100
|
```sh
|
96
|
-
npx @shivasankaran18/stackd
|
101
|
+
npx @shivasankaran18/create-stackd setup
|
97
102
|
```
|
98
103
|
|
99
104
|
## ⚡ Usage
|
package/apps/cli/src/cli.ts
CHANGED
@@ -15,7 +15,6 @@ export interface ProjectConfig {
|
|
15
15
|
orm: string;
|
16
16
|
auth: string;
|
17
17
|
dbUrl: string;
|
18
|
-
ui: string;
|
19
18
|
}
|
20
19
|
|
21
20
|
interface Answers {
|
@@ -28,8 +27,7 @@ interface Answers {
|
|
28
27
|
orm: string;
|
29
28
|
auth: string;
|
30
29
|
dbUrl: string;
|
31
|
-
projectName:
|
32
|
-
ui: string;
|
30
|
+
projectName:string;
|
33
31
|
}
|
34
32
|
|
35
33
|
const showBanner = () => {
|
@@ -69,9 +67,7 @@ const CHOICES = {
|
|
69
67
|
MONGOOSE: 'Mongoose',
|
70
68
|
JWT: 'JWT',
|
71
69
|
NEXTAUTH: 'NextAuth',
|
72
|
-
PASSPORT: 'Passport'
|
73
|
-
TAILWIND: 'Tailwind CSS',
|
74
|
-
SHADCN: 'shadcn/ui + Tailwind'
|
70
|
+
PASSPORT: 'Passport'
|
75
71
|
};
|
76
72
|
|
77
73
|
program
|
@@ -128,27 +124,6 @@ program
|
|
128
124
|
}
|
129
125
|
]);
|
130
126
|
|
131
|
-
// Add UI framework selection after frontend
|
132
|
-
let uiChoice = { ui: CHOICES.NONE };
|
133
|
-
if (frontendChoice.frontend !== chalk.green(CHOICES.DJANGO_TEMPLATES) &&
|
134
|
-
frontendChoice.frontend !== CHOICES.SKIP) {
|
135
|
-
uiChoice = await inquirer.prompt([
|
136
|
-
{
|
137
|
-
type: 'list',
|
138
|
-
name: 'ui',
|
139
|
-
message: chalk.magenta.bold('🎨 Choose a UI framework:'),
|
140
|
-
choices: (answers) => {
|
141
|
-
const isReact = frontendChoice.frontend.includes('React');
|
142
|
-
return [
|
143
|
-
chalk.blue(CHOICES.TAILWIND),
|
144
|
-
...(isReact ? [chalk.cyan(CHOICES.SHADCN)] : []),
|
145
|
-
CHOICES.NONE
|
146
|
-
];
|
147
|
-
},
|
148
|
-
default: CHOICES.NONE,
|
149
|
-
}
|
150
|
-
]);
|
151
|
-
}
|
152
127
|
|
153
128
|
const backendChoice = await inquirer.prompt([
|
154
129
|
{
|
@@ -235,7 +210,6 @@ program
|
|
235
210
|
const answers = {
|
236
211
|
...projectSettings,
|
237
212
|
...frontendChoice,
|
238
|
-
...uiChoice,
|
239
213
|
...backendChoice,
|
240
214
|
...databaseChoice,
|
241
215
|
...ormChoice,
|
@@ -16,8 +16,6 @@ import chalk from 'chalk';
|
|
16
16
|
import ora from 'ora';
|
17
17
|
import { mkdir } from 'fs/promises';
|
18
18
|
import { ProjectConfig } from '../cli.js';
|
19
|
-
import { setupShadcn } from '../scripts/ui/shadcn.js';
|
20
|
-
import { setupTailwindCSS } from '../scripts/ui/tailwindcss.js';
|
21
19
|
|
22
20
|
const emitLog = (message: string): void => {
|
23
21
|
console.log(`[Emit Logs]: ${message}`);
|
@@ -66,24 +64,6 @@ export async function createProject(projectName: string, options: ProjectConfig)
|
|
66
64
|
break;
|
67
65
|
}
|
68
66
|
|
69
|
-
// UI Framework setup
|
70
|
-
if (options.ui !== 'None' && options.frontend !== 'Django Templates' && options.frontend !== 'Skip') {
|
71
|
-
spinner.text = 'Setting up UI framework...';
|
72
|
-
switch(options.ui) {
|
73
|
-
case 'Tailwind CSS':
|
74
|
-
await setupTailwindCSS(config, projectDir, emitLog);
|
75
|
-
break;
|
76
|
-
case 'shadcn/ui + Tailwind':
|
77
|
-
await setupTailwindCSS(config, projectDir, emitLog);
|
78
|
-
// @ts-ignore
|
79
|
-
await setupShadcn(config, projectDir, emitLog);
|
80
|
-
break;
|
81
|
-
default:
|
82
|
-
emitLog('Unknown UI framework choice');
|
83
|
-
break;
|
84
|
-
}
|
85
|
-
}
|
86
|
-
|
87
67
|
// Backend setup
|
88
68
|
spinner.text = 'Setting up backend...';
|
89
69
|
switch(options.backend) {
|
@@ -163,16 +143,6 @@ export async function createProject(projectName: string, options: ProjectConfig)
|
|
163
143
|
}
|
164
144
|
}
|
165
145
|
|
166
|
-
// Add UI-specific instructions
|
167
|
-
if (options.ui !== 'None') {
|
168
|
-
console.log(chalk.yellow('\nUI Framework Setup:'));
|
169
|
-
if (options.ui === 'Tailwind CSS') {
|
170
|
-
console.log(chalk.cyan(' Tailwind CSS is ready to use'));
|
171
|
-
} else if (options.ui === 'shadcn/ui + Tailwind') {
|
172
|
-
console.log(chalk.cyan(' Run `npx shadcn-ui@latest init` to complete shadcn/ui setup'));
|
173
|
-
}
|
174
|
-
}
|
175
|
-
|
176
146
|
// Add database specific instructions
|
177
147
|
if (options.database !== 'Skip') {
|
178
148
|
console.log(chalk.yellow('\nDatabase Setup:'));
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
2
|
+
import { join } from 'node:path';
|
3
|
+
import fs from 'fs';
|
4
|
+
|
5
|
+
export async function jwtAuthts(config, projectDir,emitLog) {
|
6
|
+
emitLog('Installing jsonwebtoken...');
|
7
|
+
const packageJsonPath = join(projectDir, 'backend','package.json');
|
8
|
+
const jsonData = await JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
9
|
+
emitLog('Updating package.json...');
|
10
|
+
jsonData.dependencies = jsonData.dependencies || {};
|
11
|
+
jsonData.dependencies["jsonwebtoken"] = "^9.0.2";
|
12
|
+
|
13
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(jsonData, null, 2), 'utf8');
|
14
|
+
emitLog('Writing jwt.ts...');
|
15
|
+
const jwtAuthFile = `
|
16
|
+
const jwt = require('jsonwebtoken');
|
17
|
+
export const authenticateToken = (req:any, res:any, next:any) => {
|
18
|
+
const token = req.header('Authorization')?.split(' ')[1];
|
19
|
+
if (!token) return res.status(401).json({ error: 'Access Denied' });
|
20
|
+
|
21
|
+
jwt.verify(token, process.env.JWT_SECRET, (err:any, user:any) => {
|
22
|
+
if (err) return res.status(403).json({ error: 'Invalid Token' });
|
23
|
+
req.user = user;
|
24
|
+
next();
|
25
|
+
});
|
26
|
+
};
|
27
|
+
`;
|
28
|
+
emitLog('Writing middleware.ts...');
|
29
|
+
const middlewareDir = join(projectDir, 'backend', 'src', 'middleware');
|
30
|
+
await mkdir(middlewareDir, { recursive: true });
|
31
|
+
emitLog('Writing middleware.ts...');
|
32
|
+
await writeFile(join(middlewareDir, 'middleware.ts'), jwtAuthFile, 'utf8');
|
33
|
+
emitLog('✅ JWT authentication setup completed successfully!');
|
34
|
+
}
|
35
|
+
|
36
|
+
export async function jwtAuthdjango(config , projectDir ,emitLog) {
|
37
|
+
emitLog('Installing jsonwebtoken...');
|
38
|
+
const settingsPath = join(projectDir, 'backend', 'core', 'settings.py');
|
39
|
+
emitLog('Updating settings.py...');
|
40
|
+
try {
|
41
|
+
let settingsContent = fs.readFileSync(settingsPath, 'utf8');
|
42
|
+
const restFrameworkSettings = `
|
43
|
+
|
44
|
+
REST_FRAMEWORK = {
|
45
|
+
'DEFAULT_AUTHENTICATION_CLASSES': [
|
46
|
+
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
47
|
+
],
|
48
|
+
}
|
49
|
+
`;
|
50
|
+
|
51
|
+
const installedAppsIndex = settingsContent.indexOf('INSTALLED_APPS');
|
52
|
+
const insertPosition = settingsContent.indexOf(']', installedAppsIndex) + 1;
|
53
|
+
|
54
|
+
settingsContent =
|
55
|
+
settingsContent.slice(0, insertPosition) +
|
56
|
+
restFrameworkSettings +
|
57
|
+
settingsContent.slice(insertPosition);
|
58
|
+
|
59
|
+
fs.writeFileSync(settingsPath, settingsContent, 'utf8');
|
60
|
+
emitLog('Writing urls.py...');
|
61
|
+
let urlsContent = fs.readFileSync(`${projectDir}/backend/core/urls.py`, 'utf8');
|
62
|
+
const newUrlsContent = `from django.contrib import admin
|
63
|
+
from django.urls import path, include
|
64
|
+
from rest_framework_simplejwt import views as jwt_views
|
65
|
+
|
66
|
+
urlpatterns = [
|
67
|
+
path('admin/', admin.site.urls),
|
68
|
+
path('api/token/',
|
69
|
+
jwt_views.TokenObtainPairView.as_view(),
|
70
|
+
name='token_obtain_pair'),
|
71
|
+
path('api/token/refresh/',
|
72
|
+
jwt_views.TokenRefreshView.as_view(),
|
73
|
+
name='token_refresh'),
|
74
|
+
path('', include('main.urls')),
|
75
|
+
]
|
76
|
+
`;
|
77
|
+
fs.writeFileSync(`${projectDir}/backend/core/urls.py`, newUrlsContent, 'utf8');
|
78
|
+
emitLog('✅ JWT authentication setup completed successfully!');
|
79
|
+
} catch (error) {
|
80
|
+
console.error('Error configuring Django settings:', error);
|
81
|
+
throw error;
|
82
|
+
}
|
83
|
+
}
|
@@ -0,0 +1,146 @@
|
|
1
|
+
import { join } from 'node:path'
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
3
|
+
import 'dotenv/config'
|
4
|
+
|
5
|
+
export async function setupNextAuth(config, projectDir,emitLog) {
|
6
|
+
try {
|
7
|
+
const authDir = join(projectDir, 'frontend', 'src', 'app', 'api', 'auth');
|
8
|
+
await mkdir(authDir, { recursive: true });
|
9
|
+
emitLog('Creating auth directory...');
|
10
|
+
const routeCode = `
|
11
|
+
import NextAuth from "next-auth";
|
12
|
+
import { AuthOptions } from "next-auth";
|
13
|
+
import GithubProvider from "next-auth/providers/github";
|
14
|
+
import GoogleProvider from "next-auth/providers/google";
|
15
|
+
import CredentialsProvider from "next-auth/providers/credentials";
|
16
|
+
export const authOptions: AuthOptions = {
|
17
|
+
providers: [
|
18
|
+
GithubProvider({
|
19
|
+
clientId: process.env.GITHUB_ID!,
|
20
|
+
clientSecret: process.env.GITHUB_SECRET!,
|
21
|
+
}),
|
22
|
+
GoogleProvider({
|
23
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
24
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
25
|
+
}),
|
26
|
+
CredentialsProvider({
|
27
|
+
name: 'Credentials',
|
28
|
+
credentials: {
|
29
|
+
email: { label: "Email", type: "email" },
|
30
|
+
password: { label: "Password", type: "password" }
|
31
|
+
},
|
32
|
+
async authorize(credentials) {
|
33
|
+
// Add your credentials logic here
|
34
|
+
if (!credentials?.email || !credentials?.password) return null;
|
35
|
+
|
36
|
+
try {
|
37
|
+
// Example user verification
|
38
|
+
const user = { id: "1", email: credentials.email, name: "User" };
|
39
|
+
return user;
|
40
|
+
} catch (error) {
|
41
|
+
return null;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}),
|
45
|
+
],
|
46
|
+
pages: {
|
47
|
+
signIn: '/auth/signin',
|
48
|
+
signOut: '/auth/signout',
|
49
|
+
error: '/auth/error',
|
50
|
+
},
|
51
|
+
callbacks: {
|
52
|
+
async jwt({ token, user }) {
|
53
|
+
if (user) {
|
54
|
+
token.id = user.id;
|
55
|
+
}
|
56
|
+
return token;
|
57
|
+
},
|
58
|
+
async session({ session, token }) {
|
59
|
+
if (session.user) {
|
60
|
+
(session.user as any).id = token.id;
|
61
|
+
}
|
62
|
+
return session;
|
63
|
+
},
|
64
|
+
},
|
65
|
+
session: {
|
66
|
+
strategy: "jwt",
|
67
|
+
},
|
68
|
+
secret: process.env.NEXTAUTH_SECRET,
|
69
|
+
};
|
70
|
+
|
71
|
+
const handler = NextAuth(authOptions);
|
72
|
+
export { handler as GET, handler as POST };
|
73
|
+
`;
|
74
|
+
|
75
|
+
await writeFile(
|
76
|
+
join(authDir, 'route.ts'),
|
77
|
+
routeCode.trim() + '\n'
|
78
|
+
);
|
79
|
+
|
80
|
+
const utilsDir = join(projectDir, 'frontend', 'src', 'utils');
|
81
|
+
await mkdir(utilsDir, { recursive: true });
|
82
|
+
emitLog('Creating utils directory...');
|
83
|
+
const utilsCode = `
|
84
|
+
import { getServerSession } from "next-auth/next";
|
85
|
+
import { authOptions } from "@/app/api/auth/route";
|
86
|
+
|
87
|
+
export async function getSession() {
|
88
|
+
return await getServerSession(authOptions);
|
89
|
+
}
|
90
|
+
|
91
|
+
export async function getCurrentUser() {
|
92
|
+
const session = await getSession();
|
93
|
+
return session?.user;
|
94
|
+
}
|
95
|
+
|
96
|
+
export async function isAuthenticated() {
|
97
|
+
const session = await getSession();
|
98
|
+
return !!session;
|
99
|
+
}
|
100
|
+
`;
|
101
|
+
emitLog('Writing utils.ts...');
|
102
|
+
await writeFile(
|
103
|
+
join(utilsDir, 'auth.ts'),
|
104
|
+
utilsCode.trim() + '\n'
|
105
|
+
);
|
106
|
+
|
107
|
+
const envContent = `
|
108
|
+
# NextAuth Configuration
|
109
|
+
NEXTAUTH_SECRET=your-secret-key-here
|
110
|
+
NEXTAUTH_URL=http://localhost:${config.frontendPort}
|
111
|
+
|
112
|
+
# OAuth Providers
|
113
|
+
GITHUB_ID=your-github-id
|
114
|
+
GITHUB_SECRET=your-github-secret
|
115
|
+
|
116
|
+
GOOGLE_CLIENT_ID=your-google-client-id
|
117
|
+
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
118
|
+
`;
|
119
|
+
emitLog('Writing .env file...');
|
120
|
+
await writeFile(
|
121
|
+
join(projectDir, 'frontend', '.env'),
|
122
|
+
envContent.trim() + '\n'
|
123
|
+
);
|
124
|
+
emitLog('Writing AuthProvider.tsx...');
|
125
|
+
const providerCode = `
|
126
|
+
'use client';
|
127
|
+
|
128
|
+
import { SessionProvider } from "next-auth/react";
|
129
|
+
|
130
|
+
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
131
|
+
return <SessionProvider>{children}</SessionProvider>;
|
132
|
+
}
|
133
|
+
`;
|
134
|
+
emitLog('Creating components directory...');
|
135
|
+
const providersDir = join(projectDir, 'frontend', 'src', 'components', 'auth');
|
136
|
+
await mkdir(providersDir, { recursive: true });
|
137
|
+
await writeFile(
|
138
|
+
join(providersDir, 'AuthProvider.tsx'),
|
139
|
+
providerCode.trim() + '\n'
|
140
|
+
);
|
141
|
+
emitLog('✅ NextAuth setup completed successfully!');
|
142
|
+
} catch (error) {
|
143
|
+
emitLog(`❌ Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
144
|
+
throw error;
|
145
|
+
}
|
146
|
+
}
|
@@ -0,0 +1,234 @@
|
|
1
|
+
import { join } from 'node:path'
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
3
|
+
|
4
|
+
export async function setupPassport(config, projectDir,emitLog) {
|
5
|
+
emitLog('Setting up Passport...');
|
6
|
+
try {
|
7
|
+
const backendDir = join(projectDir, 'backend');
|
8
|
+
const authDir = join(backendDir, 'src', 'auth');
|
9
|
+
const configDir = join(backendDir, 'src', 'config');
|
10
|
+
emitLog('Creating auth and config directories...');
|
11
|
+
await mkdir(authDir, { recursive: true });
|
12
|
+
await mkdir(configDir, { recursive: true });
|
13
|
+
|
14
|
+
const passportConfigCode = `
|
15
|
+
import passport from 'passport';
|
16
|
+
import { Strategy as LocalStrategy } from 'passport-local';
|
17
|
+
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
|
18
|
+
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
|
19
|
+
import { User } from '../models/user.model';
|
20
|
+
import { config } from '../config';
|
21
|
+
|
22
|
+
// Local Strategy
|
23
|
+
passport.use(new LocalStrategy(
|
24
|
+
{ usernameField: 'email' },
|
25
|
+
async (email, password, done) => {
|
26
|
+
try {
|
27
|
+
const user = await User.findOne({ email });
|
28
|
+
if (!user) {
|
29
|
+
return done(null, false, { message: 'Incorrect email.' });
|
30
|
+
}
|
31
|
+
|
32
|
+
const isValid = await user.comparePassword(password);
|
33
|
+
if (!isValid) {
|
34
|
+
return done(null, false, { message: 'Incorrect password.' });
|
35
|
+
}
|
36
|
+
|
37
|
+
return done(null, user);
|
38
|
+
} catch (error) {
|
39
|
+
return done(error);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
));
|
43
|
+
|
44
|
+
// JWT Strategy
|
45
|
+
passport.use(new JwtStrategy(
|
46
|
+
{
|
47
|
+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
48
|
+
secretOrKey: config.jwt.secret,
|
49
|
+
},
|
50
|
+
async (payload, done) => {
|
51
|
+
try {
|
52
|
+
const user = await User.findById(payload.sub);
|
53
|
+
if (!user) {
|
54
|
+
return done(null, false);
|
55
|
+
}
|
56
|
+
return done(null, user);
|
57
|
+
} catch (error) {
|
58
|
+
return done(error, false);
|
59
|
+
}
|
60
|
+
}
|
61
|
+
));
|
62
|
+
|
63
|
+
// Google OAuth Strategy
|
64
|
+
passport.use(new GoogleStrategy(
|
65
|
+
{
|
66
|
+
clientID: config.google.clientId,
|
67
|
+
clientSecret: config.google.clientSecret,
|
68
|
+
callbackURL: '/auth/google/callback',
|
69
|
+
},
|
70
|
+
async (accessToken, refreshToken, profile, done) => {
|
71
|
+
try {
|
72
|
+
let user = await User.findOne({ googleId: profile.id });
|
73
|
+
|
74
|
+
if (!user) {
|
75
|
+
user = await User.create({
|
76
|
+
googleId: profile.id,
|
77
|
+
email: profile.emails?.[0]?.value,
|
78
|
+
name: profile.displayName,
|
79
|
+
});
|
80
|
+
}
|
81
|
+
|
82
|
+
return done(null, user);
|
83
|
+
} catch (error) {
|
84
|
+
return done(error, false);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
));
|
88
|
+
|
89
|
+
// Serialization
|
90
|
+
passport.serializeUser((user: any, done) => {
|
91
|
+
done(null, user.id);
|
92
|
+
});
|
93
|
+
|
94
|
+
passport.deserializeUser(async (id: string, done) => {
|
95
|
+
try {
|
96
|
+
const user = await User.findById(id);
|
97
|
+
done(null, user);
|
98
|
+
} catch (error) {
|
99
|
+
done(error);
|
100
|
+
}
|
101
|
+
});
|
102
|
+
|
103
|
+
export default passport;
|
104
|
+
`;
|
105
|
+
emitLog('Writing passport.config.ts...');
|
106
|
+
await writeFile(
|
107
|
+
join(authDir, 'passport.config.ts'),
|
108
|
+
passportConfigCode.trim() + '\n'
|
109
|
+
);
|
110
|
+
emitLog('Creating authentication middleware...');
|
111
|
+
const authMiddlewareCode = `
|
112
|
+
import { Request, Response, NextFunction } from 'express';
|
113
|
+
import passport from 'passport';
|
114
|
+
import jwt from 'jsonwebtoken';
|
115
|
+
import { config } from '../config';
|
116
|
+
|
117
|
+
export const authenticateJWT = passport.authenticate('jwt', { session: false });
|
118
|
+
|
119
|
+
export const authenticateLocal = (req: Request, res: Response, next: NextFunction) => {
|
120
|
+
passport.authenticate('local', { session: false }, (err, user, info) => {
|
121
|
+
if (err) {
|
122
|
+
return next(err);
|
123
|
+
}
|
124
|
+
if (!user) {
|
125
|
+
return res.status(401).json({ message: info?.message || 'Authentication failed' });
|
126
|
+
}
|
127
|
+
|
128
|
+
const token = jwt.sign({ sub: user.id }, config.jwt.secret, {
|
129
|
+
expiresIn: config.jwt.expiresIn,
|
130
|
+
});
|
131
|
+
|
132
|
+
req.user = user;
|
133
|
+
res.locals.token = token;
|
134
|
+
next();
|
135
|
+
})(req, res, next);
|
136
|
+
};
|
137
|
+
|
138
|
+
export const isAuthenticated = (req: Request, res: Response, next: NextFunction) => {
|
139
|
+
if (req.isAuthenticated()) {
|
140
|
+
return next();
|
141
|
+
}
|
142
|
+
res.status(401).json({ message: 'Unauthorized' });
|
143
|
+
};
|
144
|
+
`;
|
145
|
+
emitLog('Writing middleware.ts...');
|
146
|
+
await writeFile(
|
147
|
+
join(authDir, 'middleware.ts'),
|
148
|
+
authMiddlewareCode.trim() + '\n'
|
149
|
+
);
|
150
|
+
emitLog('Creating authentication routes...');
|
151
|
+
const authRoutesCode = `
|
152
|
+
import { Router } from 'express';
|
153
|
+
import passport from 'passport';
|
154
|
+
import { authenticateLocal } from './middleware';
|
155
|
+
|
156
|
+
const router = Router();
|
157
|
+
|
158
|
+
router.post('/login', authenticateLocal, (req, res) => {
|
159
|
+
res.json({
|
160
|
+
user: req.user,
|
161
|
+
token: res.locals.token,
|
162
|
+
});
|
163
|
+
});
|
164
|
+
|
165
|
+
router.post('/register', async (req, res) => {
|
166
|
+
// Add your registration logic here
|
167
|
+
});
|
168
|
+
|
169
|
+
router.get('/google',
|
170
|
+
passport.authenticate('google', { scope: ['profile', 'email'] })
|
171
|
+
);
|
172
|
+
|
173
|
+
router.get('/google/callback',
|
174
|
+
passport.authenticate('google', { session: false }),
|
175
|
+
(req, res) => {
|
176
|
+
// Handle successful authentication
|
177
|
+
res.redirect('/');
|
178
|
+
}
|
179
|
+
);
|
180
|
+
|
181
|
+
router.post('/logout', (req, res) => {
|
182
|
+
req.logout();
|
183
|
+
res.json({ message: 'Logged out successfully' });
|
184
|
+
});
|
185
|
+
|
186
|
+
export default router;
|
187
|
+
`;
|
188
|
+
emitLog('Writing routes.ts...');
|
189
|
+
await writeFile(
|
190
|
+
join(authDir, 'routes.ts'),
|
191
|
+
authRoutesCode.trim() + '\n'
|
192
|
+
);
|
193
|
+
emitLog('Creating configuration file...');
|
194
|
+
const configCode = `
|
195
|
+
export const config = {
|
196
|
+
jwt: {
|
197
|
+
secret: process.env.JWT_SECRET || 'your-secret-key',
|
198
|
+
expiresIn: '1d',
|
199
|
+
},
|
200
|
+
google: {
|
201
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
202
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
203
|
+
},
|
204
|
+
session: {
|
205
|
+
secret: process.env.SESSION_SECRET || 'session-secret',
|
206
|
+
},
|
207
|
+
};
|
208
|
+
`;
|
209
|
+
emitLog('Writing index.ts...');
|
210
|
+
await writeFile(
|
211
|
+
join(configDir, 'index.ts'),
|
212
|
+
configCode.trim() + '\n'
|
213
|
+
);
|
214
|
+
emitLog('Adding environment variables...');
|
215
|
+
const envContent = `
|
216
|
+
# Authentication
|
217
|
+
JWT_SECRET=your-jwt-secret-key
|
218
|
+
SESSION_SECRET=your-session-secret
|
219
|
+
|
220
|
+
# Google OAuth
|
221
|
+
GOOGLE_CLIENT_ID=your-google-client-id
|
222
|
+
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
223
|
+
`;
|
224
|
+
emitLog('Writing .env file...');
|
225
|
+
await writeFile(
|
226
|
+
join(backendDir, '.env'),
|
227
|
+
envContent.trim() + '\n'
|
228
|
+
);
|
229
|
+
emitLog('✅ Passport.js setup completed successfully!');
|
230
|
+
} catch (error) {
|
231
|
+
emitLog(`❌ Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
232
|
+
throw error;
|
233
|
+
}
|
234
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { exec } from 'child_process';
|
2
|
+
import { mkdirSync } from 'fs';
|
3
|
+
import util from 'util';
|
4
|
+
|
5
|
+
const execAsync = util.promisify(exec);
|
6
|
+
|
7
|
+
export async function installDjangoDependencies(projectPath) {
|
8
|
+
try {
|
9
|
+
|
10
|
+
mkdirSync(`${projectPath}/backend`, { recursive: true });
|
11
|
+
|
12
|
+
await execAsync('python -m venv venv', { cwd: `${projectPath}/backend` });
|
13
|
+
|
14
|
+
const pythonCmd = process.platform === 'win32' ? 'venv\\Scripts\\python.exe' : 'venv/bin/python';
|
15
|
+
|
16
|
+
await execAsync(`${pythonCmd} -m pip install django djangorestframework django-cors-headers djangorestframework_simplejwt`, { cwd: `${projectPath}/backend` });
|
17
|
+
|
18
|
+
await execAsync(`${pythonCmd} -m django startproject core .`, { cwd: `${projectPath}/backend` });
|
19
|
+
|
20
|
+
await execAsync(`${pythonCmd} manage.py startapp main`, { cwd: `${projectPath}/backend` });
|
21
|
+
|
22
|
+
console.log("Django project setup completed!");
|
23
|
+
|
24
|
+
} catch (error) {
|
25
|
+
console.error('Error installing Django dependencies:', error);
|
26
|
+
throw error;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
export default installDjangoDependencies;
|