ca-grow-ops-manager/backend
fullsizemalt c74f37783f
Some checks are pending
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run
fix(backend): Add localhost origins for Capacitor CORS
2026-01-06 23:14:35 -08:00
..
prisma feat(android): Add Capacitor for Android APK build 2026-01-06 21:56:28 -08:00
src fix(backend): Add localhost origins for Capacitor CORS 2026-01-06 23:14:35 -08:00
Dockerfile fix: Build & Runtime Fixes (TS Lax, Vite Types, Backend OpenSSL) 2025-12-09 11:18:04 -08:00
jest.config.js feat: Full Spec Kit compliance implementation 2025-12-11 09:53:32 -08:00
package-lock.json feat(backend): Add CORS support for Capacitor app 2026-01-06 23:08:15 -08:00
package.json feat(backend): Add CORS support for Capacitor app 2026-01-06 23:08:15 -08:00
README.md Initial commit: Spec Kit foundation complete 2025-12-08 23:54:12 -08:00
tsconfig.json fix: Add missing project files for backend/frontend build 2025-12-09 00:23:28 -08:00

CA Grow Ops Manager — Backend

Version: 0.1.0
Stack: TypeScript, Node.js, Express/Fastify, PostgreSQL, Prisma


Overview

The backend is a feature-first, domain-driven Node.js application that provides typed HTTP/JSON APIs for all platform features. It uses Prisma ORM for type-safe database access and follows strict security and testing practices.


Tech Stack

  • Language: TypeScript 5.x
  • Runtime: Node.js 20.x LTS
  • Framework: Express or Fastify (to be decided during implementation)
  • Database: PostgreSQL 15.x
  • ORM: Prisma 5.x
  • Authentication: JWT (access + refresh tokens)
  • Testing: Jest + Supertest
  • Validation: Zod or Joi
  • Logging: Winston or Pino
  • Email: SendGrid or Mailgun

Project Structure

backend/
├── src/
│   ├── modules/              # Feature-first modules
│   │   ├── cultivation-ops/  # Tasks, batches, rooms
│   │   │   ├── tasks/
│   │   │   │   ├── tasks.controller.ts
│   │   │   │   ├── tasks.service.ts
│   │   │   │   ├── tasks.schema.ts
│   │   │   │   └── tasks.test.ts
│   │   │   ├── batches/
│   │   │   └── rooms/
│   │   ├── compliance/       # Documents, audit packets
│   │   ├── labor/            # Timeclock, hours, reports
│   │   ├── inventory/        # Materials, lots, transactions
│   │   ├── integrations/     # METRC, environmental, hardware
│   │   ├── communications/   # Comments, announcements, notifications
│   │   └── auth/             # Authentication, authorization
│   ├── shared/               # Shared utilities
│   │   ├── middleware/       # Auth, validation, error handling
│   │   ├── utils/            # Helpers, formatters
│   │   └── types/            # Shared TypeScript types
│   ├── prisma/               # Prisma schema and migrations
│   │   ├── schema.prisma
│   │   └── migrations/
│   ├── config/               # Configuration
│   │   ├── database.ts
│   │   ├── auth.ts
│   │   └── email.ts
│   ├── app.ts                # Express/Fastify app setup
│   └── server.ts             # Server entry point
├── tests/                    # Integration tests
│   ├── setup.ts
│   └── integration/
├── .env.example              # Environment variables template
├── package.json
├── tsconfig.json
└── README.md

Domain Modules

Each module follows this structure:

module-name/
├── entity.controller.ts   # HTTP route handlers
├── entity.service.ts      # Business logic
├── entity.schema.ts       # Validation schemas (Zod/Joi)
├── entity.test.ts         # Unit tests
└── index.ts               # Module exports

Example: Tasks Module

// tasks.controller.ts
export const getTasks = async (req, res) => {
  const tasks = await tasksService.findAll(req.query);
  res.json(tasks);
};

// tasks.service.ts
export const findAll = async (filters) => {
  return prisma.task.findMany({ where: filters });
};

// tasks.schema.ts
export const createTaskSchema = z.object({
  name: z.string().min(1),
  dueDate: z.string().datetime(),
  assigneeId: z.string().optional(),
});

API Design

RESTful Conventions

  • GET /api/tasks - List tasks
  • GET /api/tasks/:id - Get task detail
  • POST /api/tasks - Create task
  • PATCH /api/tasks/:id - Update task
  • DELETE /api/tasks/:id - Delete task

Response Format

{
  "success": true,
  "data": { ... },
  "meta": {
    "page": 1,
    "pageSize": 20,
    "total": 100
  }
}

Error Format

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": [
      { "field": "dueDate", "message": "Required" }
    ]
  }
}

Authentication & Authorization

JWT-Based Auth

  • Access Token: 15-minute expiry, stored in httpOnly cookie
  • Refresh Token: 7-day expiry, stored in localStorage
  • Token Payload: { userId, roleId, iat, exp }

RBAC (Role-Based Access Control)

Roles:

  • Owner: Full access
  • Compliance Manager: Full compliance, read-only operations
  • Head Grower: Full cultivation ops, read-only compliance
  • Staff: Limited access to tasks and timeclock
  • Accountant: Read-only reports

Middleware:

export const requireRole = (roles: string[]) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
};

Database (Prisma)

Schema Example

model Task {
  id          String   @id @default(cuid())
  name        String
  description String?
  status      TaskStatus @default(PENDING)
  dueDate     DateTime
  assigneeId  String?
  assignee    User?    @relation(fields: [assigneeId], references: [id])
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

enum TaskStatus {
  PENDING
  IN_PROGRESS
  COMPLETE
  OVERDUE
}

Migrations

# Create migration
npx prisma migrate dev --name add_tasks_table

# Apply migrations
npx prisma migrate deploy

# Generate Prisma Client
npx prisma generate

Testing

Unit Tests (Jest)

describe('TasksService', () => {
  it('should create a task', async () => {
    const task = await tasksService.create({
      name: 'Water plants',
      dueDate: new Date(),
    });
    expect(task.name).toBe('Water plants');
  });
});

Integration Tests (Supertest)

describe('POST /api/tasks', () => {
  it('should create a task', async () => {
    const res = await request(app)
      .post('/api/tasks')
      .send({ name: 'Water plants', dueDate: '2025-12-10' })
      .expect(201);
    expect(res.body.data.name).toBe('Water plants');
  });
});

Environment Variables

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/ca_grow_ops

# Auth
JWT_SECRET=your-secret-key
JWT_ACCESS_EXPIRY=15m
JWT_REFRESH_EXPIRY=7d

# Email
EMAIL_SERVICE=sendgrid
EMAIL_API_KEY=your-api-key
EMAIL_FROM=noreply@example.com

# Integrations
METRC_API_KEY=your-metrc-key
METRC_API_URL=https://api-ca.metrc.com

# Server
PORT=3000
NODE_ENV=development

Development Workflow

Setup

# Install dependencies
npm install

# Set up environment variables
cp .env.example .env

# Run database migrations
npx prisma migrate dev

# Seed database (optional)
npx prisma db seed

# Start dev server
npm run dev

Scripts

{
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "lint": "eslint src --ext .ts",
    "format": "prettier --write src",
    "migrate": "prisma migrate dev",
    "migrate:deploy": "prisma migrate deploy",
    "prisma:generate": "prisma generate"
  }
}

Security Best Practices

  • Input Validation: Validate all inputs with Zod/Joi
  • SQL Injection: Use Prisma parameterized queries
  • XSS: Sanitize user inputs; use Content Security Policy
  • CSRF: Use CSRF tokens for state-changing operations
  • Rate Limiting: Limit API requests per user/IP
  • Audit Logging: Log all critical operations

Deployment

Docker

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npx prisma generate
RUN npm run build
CMD ["npm", "start"]

Docker Compose

version: '3.8'
services:
  backend:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://user:password@db:5432/ca_grow_ops
    depends_on:
      - db
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: ca_grow_ops
    volumes:
      - postgres_data:/var/lib/postgresql/data
volumes:
  postgres_data:

Next Steps

  1. Choose Framework: Decide between Express and Fastify
  2. Set Up Project: Initialize Node.js project with TypeScript
  3. Configure Prisma: Set up Prisma schema and migrations
  4. Implement Auth: Build authentication and RBAC
  5. Build Modules: Implement feature modules per spec
  6. Write Tests: Unit and integration tests for all modules
  7. Deploy: Set up CI/CD and deploy to staging

Resources