feat: Photo persistence and user creation

- Change STORAGE_PATH from /tmp to /app/photos
- Add photos_data volume to docker-compose.yml for persistence
- Add createUser controller with bcrypt password hashing
- Add POST /users route for employee creation
This commit is contained in:
fullsizemalt 2026-01-12 15:21:37 -08:00
parent 3a62e94ad8
commit 7607dff622
4 changed files with 64 additions and 3 deletions

View file

@ -57,3 +57,60 @@ export const updateUser = async (request: FastifyRequest, reply: FastifyReply) =
return reply.code(500).send({ message: 'Failed to update user' }); return reply.code(500).send({ message: 'Failed to update user' });
} }
}; };
export const createUser = async (request: FastifyRequest, reply: FastifyReply) => {
const data = request.body as {
email: string;
password: string;
name?: string;
role?: string;
roleId?: string;
rate?: number;
};
// Validate required fields
if (!data.email || !data.password) {
return reply.code(400).send({ message: 'Email and password are required' });
}
try {
// Check if user already exists
const existing = await request.server.prisma.user.findUnique({
where: { email: data.email }
});
if (existing) {
return reply.code(409).send({ message: 'User with this email already exists' });
}
// Hash password with bcrypt
const bcrypt = await import('bcrypt');
const hashedPassword = await bcrypt.hash(data.password, 10);
// Create user
const user = await request.server.prisma.user.create({
data: {
email: data.email,
password: hashedPassword,
name: data.name || data.email.split('@')[0],
role: (data.role as any) || 'WORKER',
...(data.roleId && { roleId: data.roleId }),
...(data.rate !== undefined && { rate: data.rate }),
},
select: {
id: true,
email: true,
name: true,
role: true,
userRole: true,
rate: true,
createdAt: true,
}
});
return reply.code(201).send(user);
} catch (error) {
request.log.error(error);
return reply.code(500).send({ message: 'Failed to create user' });
}
};

View file

@ -4,8 +4,8 @@ import path from 'path';
import crypto from 'crypto'; import crypto from 'crypto';
import sharp from 'sharp'; import sharp from 'sharp';
// Storage base path - configurable via env // Storage base path - uses mounted volume for persistence
const STORAGE_PATH = process.env.STORAGE_PATH || '/tmp/ca-grow-ops-manager/photos'; const STORAGE_PATH = process.env.STORAGE_PATH || '/app/photos';
// Image size configurations per spec // Image size configurations per spec
const IMAGE_SIZES = { const IMAGE_SIZES = {

View file

@ -1,7 +1,8 @@
import { FastifyInstance } from 'fastify'; import { FastifyInstance } from 'fastify';
import { getUsers, updateUser } from '../controllers/users.controller'; import { getUsers, updateUser, createUser } from '../controllers/users.controller';
export async function userRoutes(server: FastifyInstance) { export async function userRoutes(server: FastifyInstance) {
server.get('/', getUsers); server.get('/', getUsers);
server.post('/', createUser);
server.patch('/:id', updateUser); server.patch('/:id', updateUser);
} }

View file

@ -57,6 +57,8 @@ services:
timeout: 10s timeout: 10s
retries: 3 retries: 3
start_period: 40s start_period: 40s
volumes:
- photos_data:/app/photos
go2rtc: go2rtc:
image: alexxit/go2rtc:latest image: alexxit/go2rtc:latest
@ -121,3 +123,4 @@ networks:
volumes: volumes:
db_data: db_data:
photos_data: