First push
This commit is contained in:
1 parent
db25d6dbda
commit
7299578599
48 files changed
+17513
-1
No files matched your search
@@ -0,0 +1,12 @@
|
||||
# Server Configuration
|
||||
PORT=3001
|
||||
NODE_ENV=development
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET=your-secret-key-change-this-in-production
|
||||
|
||||
# MongoDB Configuration
|
||||
MONGODB_URI=mongodb://localhost:27017/ui-mockup-generator
|
||||
|
||||
# Cors Configuration
|
||||
CORS_ORIGIN=http://localhost:3000
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "ui-mockup-generator-server",
|
||||
"version": "0.1.0",
|
||||
"description": "Backend server for UI Mockup Generator",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start": "node dist/index.js",
|
||||
"dev": "nodemon --watch src --exec ts-node src/index.ts",
|
||||
"build": "tsc",
|
||||
"test": "jest",
|
||||
"lint": "eslint src --ext ts --report-unused-disable-directives --max-warnings 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"mongoose": "^8.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"multer": "^1.4.5-lts.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.16",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.10",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
"@types/multer": "^1.4.10",
|
||||
"@types/node": "^20.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"eslint": "^8.54.0",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Request, Response } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import User from '../models/User';
|
||||
|
||||
// JWT secret key
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
|
||||
// Register a new user
|
||||
export const register = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { username, email, password } = req.body;
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await User.findOne({ email });
|
||||
if (existingUser) {
|
||||
return res.status(400).json({ message: 'User already exists' });
|
||||
}
|
||||
|
||||
// Create new user
|
||||
const newUser = new User({
|
||||
username,
|
||||
email,
|
||||
password, // Note: Password should be hashed in the User model
|
||||
});
|
||||
|
||||
await newUser.save();
|
||||
|
||||
// Generate JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: newUser._id },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
token,
|
||||
user: {
|
||||
id: newUser._id,
|
||||
username: newUser.username,
|
||||
email: newUser.email,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
};
|
||||
|
||||
// Login
|
||||
export const login = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Find user
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
return res.status(400).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Check password (should use compare method in a real app)
|
||||
const isMatch = await user.comparePassword(password);
|
||||
if (!isMatch) {
|
||||
return res.status(400).json({ message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user._id },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
};
|
||||
|
||||
// Logout (in a JWT-based auth, this is mostly client-side)
|
||||
export const logout = (req: Request, res: Response) => {
|
||||
// In a real app, you might want to blacklist the token
|
||||
// or handle refresh tokens
|
||||
res.status(200).json({ message: 'Logged out successfully' });
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
import { Request, Response } from 'express';
|
||||
import Project from '../models/Project';
|
||||
|
||||
// Get all projects
|
||||
export const getProjects = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const projects = await Project.find({ user: req.userId });
|
||||
res.status(200).json(projects);
|
||||
} catch (error) {
|
||||
console.error('Error fetching projects:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
};
|
||||
|
||||
// Get project by ID
|
||||
export const getProjectById = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const project = await Project.findOne({
|
||||
_id: req.params.id,
|
||||
user: req.userId
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
return res.status(404).json({ message: 'Project not found' });
|
||||
}
|
||||
|
||||
res.status(200).json(project);
|
||||
} catch (error) {
|
||||
console.error('Error fetching project:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new project
|
||||
export const createProject = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { name, description, content } = req.body;
|
||||
|
||||
const newProject = new Project({
|
||||
name,
|
||||
description,
|
||||
content,
|
||||
user: req.userId,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
});
|
||||
|
||||
const savedProject = await newProject.save();
|
||||
res.status(201).json(savedProject);
|
||||
} catch (error) {
|
||||
console.error('Error creating project:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
};
|
||||
|
||||
// Update a project
|
||||
export const updateProject = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { name, description, content } = req.body;
|
||||
|
||||
const project = await Project.findOneAndUpdate(
|
||||
{ _id: req.params.id, user: req.userId },
|
||||
{
|
||||
name,
|
||||
description,
|
||||
content,
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
if (!project) {
|
||||
return res.status(404).json({ message: 'Project not found' });
|
||||
}
|
||||
|
||||
res.status(200).json(project);
|
||||
} catch (error) {
|
||||
console.error('Error updating project:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
};
|
||||
|
||||
// Delete a project
|
||||
export const deleteProject = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const project = await Project.findOneAndDelete({
|
||||
_id: req.params.id,
|
||||
user: req.userId
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
return res.status(404).json({ message: 'Project not found' });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: 'Project deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting project:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import mongoose from 'mongoose';
|
||||
import projectRoutes from './routes/projectRoutes';
|
||||
import authRoutes from './routes/authRoutes';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// Create Express app
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// API routes
|
||||
app.use('/api/projects', projectRoutes);
|
||||
app.use('/api/auth', authRoutes);
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.status(200).json({ status: 'ok' });
|
||||
});
|
||||
|
||||
// Connect to MongoDB (if available)
|
||||
const connectDB = async () => {
|
||||
try {
|
||||
if (process.env.MONGODB_URI) {
|
||||
await mongoose.connect(process.env.MONGODB_URI);
|
||||
console.log('MongoDB connected successfully');
|
||||
} else {
|
||||
console.log('MongoDB URI not provided, running without database');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('MongoDB connection error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Start server
|
||||
const startServer = async () => {
|
||||
try {
|
||||
// Connect to database (optional)
|
||||
if (process.env.MONGODB_URI) {
|
||||
await connectDB();
|
||||
}
|
||||
|
||||
// Start the server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize server
|
||||
startServer();
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
// JWT secret key
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
|
||||
// Define custom Request interface with userId
|
||||
interface AuthRequest extends Request {
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
// Middleware to authenticate JWT token
|
||||
export const auth = (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// Get token from header
|
||||
const token = req.header('x-auth-token');
|
||||
|
||||
// Check if no token
|
||||
if (!token) {
|
||||
return res.status(401).json({ message: 'No token, authorization denied' });
|
||||
}
|
||||
|
||||
// Verify token
|
||||
const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };
|
||||
|
||||
// Add user ID to request
|
||||
req.userId = decoded.userId;
|
||||
|
||||
// Continue to the next middleware/controller
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Token verification failed:', error);
|
||||
res.status(401).json({ message: 'Token is not valid' });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import mongoose, { Schema, Document } from 'mongoose';
|
||||
|
||||
export interface IProject extends Document {
|
||||
name: string;
|
||||
description?: string;
|
||||
content: any;
|
||||
user: mongoose.Types.ObjectId;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
const ProjectSchema: Schema = new Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
content: {
|
||||
type: Schema.Types.Mixed,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true,
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
updatedAt: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
});
|
||||
|
||||
export default mongoose.model<IProject>('Project', ProjectSchema);
|
||||
@@ -0,0 +1,63 @@
|
||||
import mongoose, { Schema, Document } from 'mongoose';
|
||||
import { hash, compare } from 'bcrypt';
|
||||
|
||||
export interface IUser extends Document {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
createdAt: Date;
|
||||
comparePassword(candidatePassword: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
const UserSchema: Schema = new Schema({
|
||||
username: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
trim: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
trim: true,
|
||||
lowercase: true,
|
||||
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, 'Please provide a valid email address'],
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
minlength: 6,
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
});
|
||||
|
||||
// Hash password before saving
|
||||
UserSchema.pre<IUser>('save', async function(next) {
|
||||
// Only hash the password if it's modified (or new)
|
||||
if (!this.isModified('password')) return next();
|
||||
|
||||
try {
|
||||
// Generate a salt
|
||||
const salt = 10;
|
||||
// Hash the password with the salt
|
||||
this.password = await hash(this.password, salt);
|
||||
next();
|
||||
} catch (error) {
|
||||
next(error as Error);
|
||||
}
|
||||
});
|
||||
|
||||
// Method to compare password
|
||||
UserSchema.methods.comparePassword = async function(candidatePassword: string): Promise<boolean> {
|
||||
try {
|
||||
return await compare(candidatePassword, this.password);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default mongoose.model<IUser>('User', UserSchema);
|
||||
@@ -0,0 +1,15 @@
|
||||
import express from 'express';
|
||||
import { register, login, logout } from '../controllers/authController';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Register a new user
|
||||
router.post('/register', register);
|
||||
|
||||
// Login
|
||||
router.post('/login', login);
|
||||
|
||||
// Logout
|
||||
router.post('/logout', logout);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,27 @@
|
||||
import express from 'express';
|
||||
import {
|
||||
getProjects,
|
||||
getProjectById,
|
||||
createProject,
|
||||
updateProject,
|
||||
deleteProject
|
||||
} from '../controllers/projectController';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all projects
|
||||
router.get('/', getProjects);
|
||||
|
||||
// Get project by ID
|
||||
router.get('/:id', getProjectById);
|
||||
|
||||
// Create a new project
|
||||
router.post('/', createProject);
|
||||
|
||||
// Update a project
|
||||
router.put('/:id', updateProject);
|
||||
|
||||
// Delete a project
|
||||
router.delete('/:id', deleteProject);
|
||||
|
||||
export default router;
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
import { Express, Request } from 'express';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
userId?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
}
|
||||
Reference in new issue
Block a user