✨ Features: - Modern SaaS automation platform - Next.js 15 + TypeScript frontend - Node.js + Express backend - PostgreSQL database with full schema - Docker Compose setup - Admin panel with analytics - Template marketplace (6 templates) - Integrations hub (10+ services) - Authentication & role-based access - Responsive n8n-style design 🎯 Ready for demo and deployment 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
173 lines
No EOL
4.8 KiB
TypeScript
173 lines
No EOL
4.8 KiB
TypeScript
import { Request, Response } from 'express';
|
|
import { pool } from '../db/connection';
|
|
import { hashPassword, comparePassword, generateToken, generateRandomToken } from '../utils/auth';
|
|
|
|
export const signup = async (req: Request, res: Response) => {
|
|
try {
|
|
const { email, password, first_name, last_name } = req.body;
|
|
|
|
if (!email || !password) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Email and password are required'
|
|
});
|
|
}
|
|
|
|
const existingUser = await pool.query('SELECT id FROM users WHERE email = $1', [email]);
|
|
if (existingUser.rows.length > 0) {
|
|
return res.status(409).json({
|
|
success: false,
|
|
message: 'User already exists with this email'
|
|
});
|
|
}
|
|
|
|
const passwordHash = await hashPassword(password);
|
|
const verificationToken = generateRandomToken();
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO users (email, password_hash, first_name, last_name, verification_token, is_verified)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
RETURNING id, email, first_name, last_name, role, subscription_plan, subscription_status, created_at`,
|
|
[email, passwordHash, first_name, last_name, verificationToken, false]
|
|
);
|
|
|
|
const user = result.rows[0];
|
|
const token = generateToken({
|
|
userId: user.id,
|
|
email: user.email,
|
|
role: user.role,
|
|
subscription_plan: user.subscription_plan
|
|
});
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
token,
|
|
user: {
|
|
id: user.id,
|
|
email: user.email,
|
|
first_name: user.first_name,
|
|
last_name: user.last_name,
|
|
role: user.role,
|
|
subscription_plan: user.subscription_plan,
|
|
subscription_status: user.subscription_status,
|
|
is_verified: false,
|
|
created_at: user.created_at,
|
|
updated_at: user.created_at
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Signup error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
export const login = async (req: Request, res: Response) => {
|
|
try {
|
|
const { email, password } = req.body;
|
|
|
|
if (!email || !password) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Email and password are required'
|
|
});
|
|
}
|
|
|
|
const result = await pool.query(
|
|
'SELECT id, email, password_hash, first_name, last_name, is_verified, role, subscription_plan, subscription_status, created_at, updated_at FROM users WHERE email = $1',
|
|
[email]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Invalid credentials'
|
|
});
|
|
}
|
|
|
|
const user = result.rows[0];
|
|
const isValidPassword = await comparePassword(password, user.password_hash);
|
|
|
|
if (!isValidPassword) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Invalid credentials'
|
|
});
|
|
}
|
|
|
|
const token = generateToken({
|
|
userId: user.id,
|
|
email: user.email,
|
|
role: user.role,
|
|
subscription_plan: user.subscription_plan
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
token,
|
|
user: {
|
|
id: user.id,
|
|
email: user.email,
|
|
first_name: user.first_name,
|
|
last_name: user.last_name,
|
|
is_verified: user.is_verified,
|
|
role: user.role,
|
|
subscription_plan: user.subscription_plan,
|
|
subscription_status: user.subscription_status,
|
|
created_at: user.created_at,
|
|
updated_at: user.updated_at
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
};
|
|
|
|
export const resetPassword = async (req: Request, res: Response) => {
|
|
try {
|
|
const { email } = req.body;
|
|
|
|
if (!email) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Email is required'
|
|
});
|
|
}
|
|
|
|
const user = await pool.query('SELECT id FROM users WHERE email = $1', [email]);
|
|
if (user.rows.length === 0) {
|
|
return res.json({
|
|
success: true,
|
|
message: 'If the email exists, a reset link has been sent'
|
|
});
|
|
}
|
|
|
|
const resetToken = generateRandomToken();
|
|
const resetExpires = new Date(Date.now() + 3600000); // 1 hour
|
|
|
|
await pool.query(
|
|
'UPDATE users SET reset_password_token = $1, reset_password_expires = $2 WHERE email = $3',
|
|
[resetToken, resetExpires, email]
|
|
);
|
|
|
|
// TODO: Send email with reset link
|
|
console.log(`Reset token for ${email}: ${resetToken}`);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'If the email exists, a reset link has been sent'
|
|
});
|
|
} catch (error) {
|
|
console.error('Reset password error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Internal server error'
|
|
});
|
|
}
|
|
}; |