Initial implementation of Website Change Detection Monitor MVP
Features implemented: - Backend API with Express + TypeScript - User authentication (register/login with JWT) - Monitor CRUD operations with plan-based limits - Automated change detection engine - Email alert system - Frontend with Next.js + TypeScript - Dashboard with monitor management - Login/register pages - Monitor history viewer - PostgreSQL database schema - Docker setup for local development Technical stack: - Backend: Express, TypeScript, PostgreSQL, Redis (ready) - Frontend: Next.js 14, React Query, Tailwind CSS - Database: PostgreSQL with migrations - Services: Page fetching, diff detection, email alerts Documentation: - README with full setup instructions - SETUP guide for quick start - PROJECT_STATUS with current capabilities - Complete technical specifications Ready for local testing and feature expansion. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
143
backend/src/routes/auth.ts
Normal file
143
backend/src/routes/auth.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import db from '../db';
|
||||
import {
|
||||
hashPassword,
|
||||
comparePassword,
|
||||
generateToken,
|
||||
validateEmail,
|
||||
validatePassword,
|
||||
} from '../utils/auth';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const registerSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8),
|
||||
});
|
||||
|
||||
const loginSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
});
|
||||
|
||||
// Register
|
||||
router.post('/register', async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { email, password } = registerSchema.parse(req.body);
|
||||
|
||||
if (!validateEmail(email)) {
|
||||
res.status(400).json({
|
||||
error: 'invalid_email',
|
||||
message: 'Invalid email format',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordValidation = validatePassword(password);
|
||||
if (!passwordValidation.valid) {
|
||||
res.status(400).json({
|
||||
error: 'invalid_password',
|
||||
message: 'Password does not meet requirements',
|
||||
details: passwordValidation.errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const existingUser = await db.users.findByEmail(email);
|
||||
if (existingUser) {
|
||||
res.status(409).json({
|
||||
error: 'user_exists',
|
||||
message: 'User with this email already exists',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordHash = await hashPassword(password);
|
||||
const user = await db.users.create(email, passwordHash);
|
||||
|
||||
const token = generateToken(user);
|
||||
|
||||
res.status(201).json({
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
plan: user.plan,
|
||||
createdAt: user.createdAt,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
res.status(400).json({
|
||||
error: 'validation_error',
|
||||
message: 'Invalid input',
|
||||
details: error.errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('Register error:', error);
|
||||
res.status(500).json({
|
||||
error: 'server_error',
|
||||
message: 'Failed to register user',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Login
|
||||
router.post('/login', async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { email, password } = loginSchema.parse(req.body);
|
||||
|
||||
const user = await db.users.findByEmail(email);
|
||||
if (!user) {
|
||||
res.status(401).json({
|
||||
error: 'invalid_credentials',
|
||||
message: 'Invalid email or password',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const isValidPassword = await comparePassword(password, user.passwordHash);
|
||||
if (!isValidPassword) {
|
||||
res.status(401).json({
|
||||
error: 'invalid_credentials',
|
||||
message: 'Invalid email or password',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await db.users.updateLastLogin(user.id);
|
||||
|
||||
const token = generateToken(user);
|
||||
|
||||
res.json({
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
plan: user.plan,
|
||||
createdAt: user.createdAt,
|
||||
lastLoginAt: new Date(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
res.status(400).json({
|
||||
error: 'validation_error',
|
||||
message: 'Invalid input',
|
||||
details: error.errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({
|
||||
error: 'server_error',
|
||||
message: 'Failed to login',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user