Files
website-monitor/backend/src/routes/auth.ts
Timo 2c1ec69a79 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>
2026-01-16 18:46:40 +01:00

144 lines
3.2 KiB
TypeScript

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;