From 7607dff622a93a8ebb44990fc8e6faa54cf76cc4 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:21:37 -0800 Subject: [PATCH] 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 --- backend/src/controllers/users.controller.ts | 57 +++++++++++++++++++++ backend/src/routes/upload.routes.ts | 4 +- backend/src/routes/users.routes.ts | 3 +- docker-compose.yml | 3 ++ 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/users.controller.ts b/backend/src/controllers/users.controller.ts index 2c74b1c..53eb8f5 100644 --- a/backend/src/controllers/users.controller.ts +++ b/backend/src/controllers/users.controller.ts @@ -57,3 +57,60 @@ export const updateUser = async (request: FastifyRequest, reply: FastifyReply) = 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' }); + } +}; diff --git a/backend/src/routes/upload.routes.ts b/backend/src/routes/upload.routes.ts index 94315bc..f408aeb 100644 --- a/backend/src/routes/upload.routes.ts +++ b/backend/src/routes/upload.routes.ts @@ -4,8 +4,8 @@ import path from 'path'; import crypto from 'crypto'; import sharp from 'sharp'; -// Storage base path - configurable via env -const STORAGE_PATH = process.env.STORAGE_PATH || '/tmp/ca-grow-ops-manager/photos'; +// Storage base path - uses mounted volume for persistence +const STORAGE_PATH = process.env.STORAGE_PATH || '/app/photos'; // Image size configurations per spec const IMAGE_SIZES = { diff --git a/backend/src/routes/users.routes.ts b/backend/src/routes/users.routes.ts index 9362b7e..576578c 100644 --- a/backend/src/routes/users.routes.ts +++ b/backend/src/routes/users.routes.ts @@ -1,7 +1,8 @@ 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) { server.get('/', getUsers); + server.post('/', createUser); server.patch('/:id', updateUser); } diff --git a/docker-compose.yml b/docker-compose.yml index 918d5b5..bd835b9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -57,6 +57,8 @@ services: timeout: 10s retries: 3 start_period: 40s + volumes: + - photos_data:/app/photos go2rtc: image: alexxit/go2rtc:latest @@ -121,3 +123,4 @@ networks: volumes: db_data: + photos_data: