Set up backend API server with Express and Prisma
- Initialize Node.js/Express backend project structure - Configure TypeScript for backend development - Set up Prisma ORM with MySQL database schema - Implement authentication middleware with Clerk - Create photo upload endpoint with EXIF extraction - Build report CRUD endpoints (create, read, delete) - Add file upload handling with Multer - Configure CORS and security middleware (helmet) - Set up error handling and validation - Create development environment configuration Task 21 complete: Backend foundation ready for report submissions
This commit is contained in:
56
server/src/middleware/auth.ts
Normal file
56
server/src/middleware/auth.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { clerkClient } from '@clerk/clerk-sdk-node';
|
||||
|
||||
export interface AuthRequest extends Request {
|
||||
userId?: string;
|
||||
user?: any;
|
||||
}
|
||||
|
||||
export const requireAuth = async (
|
||||
req: AuthRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({
|
||||
error: {
|
||||
code: 'UNAUTHORIZED',
|
||||
message: 'No authorization token provided',
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
// Verify the session token with Clerk
|
||||
const session = await clerkClient.sessions.verifySession(token, token);
|
||||
|
||||
if (!session) {
|
||||
return res.status(401).json({
|
||||
error: {
|
||||
code: 'INVALID_TOKEN',
|
||||
message: 'Invalid or expired token',
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Attach user ID to request
|
||||
req.userId = session.userId;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Auth middleware error:', error);
|
||||
return res.status(401).json({
|
||||
error: {
|
||||
code: 'UNAUTHORIZED',
|
||||
message: 'Authentication failed',
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
40
server/src/middleware/upload.ts
Normal file
40
server/src/middleware/upload.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import multer from 'multer';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
// Ensure upload directory exists
|
||||
const uploadDir = process.env.UPLOAD_DIR || './uploads';
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Configure storage
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, uploadDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
|
||||
},
|
||||
});
|
||||
|
||||
// File filter
|
||||
const fileFilter = (req: any, file: Express.Multer.File, cb: multer.FileFilterCallback) => {
|
||||
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'];
|
||||
|
||||
if (allowedTypes.includes(file.mimetype)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Invalid file type. Only JPEG, PNG, and WebP images are allowed.'));
|
||||
}
|
||||
};
|
||||
|
||||
// Configure multer
|
||||
export const upload = multer({
|
||||
storage,
|
||||
fileFilter,
|
||||
limits: {
|
||||
fileSize: parseInt(process.env.MAX_FILE_SIZE || '5242880'), // 5MB default
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user