First push

This commit is contained in:
Sam Redfern committed 2025-04-28 10:06:04 +10:00
1 parent db25d6dbda
commit 7299578599
48 files changed
+17513 -1

No files matched your search

+12
View File
@@ -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
+40
View File
@@ -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"
}
}
+92
View File
@@ -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' });
};
+100
View File
@@ -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' });
}
};
+63
View File
@@ -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();
+35
View File
@@ -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' });
}
};
+41
View File
@@ -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);
+63
View File
@@ -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);
+15
View File
@@ -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;
+27
View File
@@ -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;
+9
View File
@@ -0,0 +1,9 @@
import { Express, Request } from 'express';
declare global {
namespace Express {
interface Request {
userId?: string;
}
}
}
+16
View File
@@ -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"]
}