A production-ready TypeScript/Node.js application that provides a Fastify-based API server for analyzing grow operation photos and integrating with Notion. The application receives webhook requests, validates them using HMAC signatures, and processes plant photo analysis jobs with AI-powered insights.
- π Secure Webhook Handling: HMAC-SHA256 signature verification with timing-safe comparison
- π High-Performance API: Built on Fastify for optimal performance (one of the fastest Node.js frameworks)
- π Schema Validation: Runtime type checking with Zod for bulletproof data integrity
- π± Plant Photo Analysis: Process and analyze grow operation photos with AI
- π Notion Integration: Seamless integration with Notion databases for data management
- π‘οΈ Security Hardened: Multiple security layers including rate limiting and security headers
- π§ͺ Well Tested: Comprehensive test suite with 41+ tests and >60% coverage
- π Fully Documented: Complete API documentation, architecture guide, and contribution guidelines
| Category | Technology | Version | Purpose |
|---|---|---|---|
| Runtime | Node.js | >= 20 | JavaScript runtime |
| Language | TypeScript | 5.6+ | Type-safe development |
| Web Framework | Fastify | 4.x | High-performance HTTP server |
| Validation | Zod | 3.x | Runtime schema validation |
| Testing | Vitest | 2.x | Fast unit and integration testing |
| Linting | ESLint | 9.x | Code quality and consistency |
| Package Manager | pnpm | 9.x | Fast, disk-efficient package management |
Before you begin, ensure you have the following installed:
- Node.js >= 20.0.0 (Download)
- pnpm >= 9.0.0 (installed via npm or corepack)
# Check Node.js version
node --version # Should be v20.0.0 or higher
# Check or install pnpm
npx pnpm --version # If not found, install with: npm install -g pnpm@^9.0.0# Clone the repository
git clone https://github.com/stevenschling13/Notion-Grow-Ops.git
cd Notion-Grow-Ops
# Install pnpm if you haven't already (choose one method)
npm install -g pnpm@^9.0.0
# OR use corepack (recommended)
corepack enable
corepack prepare pnpm@9 --activate
# Install dependencies
pnpm installCreate a .env file in the root directory:
cp .env.example .envEdit the .env file with your configuration:
# Server Configuration
PORT=8080
# Security - REQUIRED: Generate a strong secret for HMAC verification
# Example: openssl rand -hex 32
HMAC_SECRET=your-secret-key-here-change-me
# Optional: Token to bypass rate limiting for trusted upstream services
RATE_LIMIT_BYPASS_TOKEN=your-bypass-token-hereStart the development server with hot reload:
pnpm run devThe server will start on http://localhost:8080 (or the port specified in your .env file).
Run the test suite:
# Run tests in watch mode (for development)
pnpm test
# Run tests once (for CI)
pnpm test -- --run
# Run with coverage report
pnpm test -- --coverage| Variable | Description | Required | Default | Example |
|---|---|---|---|---|
PORT |
HTTP server port | No | 8080 |
3000 |
HMAC_SECRET |
Secret key for HMAC signature verification | Yes | - | Use openssl rand -hex 32 |
RATE_LIMIT_BYPASS_TOKEN |
Token to bypass rate limiting for trusted services | No | - | secret-bypass-token-123 |
# Generate HMAC_SECRET (64-character hex string)
openssl rand -hex 32
# Generate RATE_LIMIT_BYPASS_TOKEN (32-character hex string)
openssl rand -hex 16# Development
pnpm run dev # Start dev server with hot reload (tsx watch)
# Building
pnpm run build # Compile TypeScript to JavaScript (output: dist/)
pnpm run start # Run the compiled application in production mode
# Quality Checks
pnpm run lint # Run ESLint to check code style
pnpm run typecheck # Run TypeScript type checking without emitting files
pnpm test # Run tests with Vitest (watch mode)
pnpm test -- --run # Run tests once (for CI)
# Combined
pnpm run build && pnpm run lint && pnpm run typecheck && pnpm test -- --runStart the development server:
pnpm run devThe server will start on http://localhost:8080 with hot reload enabled. Any changes to TypeScript files will automatically restart the server.
The GitHub Copilot CLI brings AI-powered assistance directly to your terminal:
# Install globally
npm install -g @github/copilot
# Authenticate with GitHub
copilot auth
# Get command suggestions
copilot suggest "run tests in watch mode"
# Explain complex commands
copilot explain "git rebase -i HEAD~3"
# Get help with Git operations
copilot suggest "undo last commit but keep changes"Benefits:
- π€ AI-powered command suggestions
- π Instant explanations of complex commands
- β‘ Faster terminal workflows
- π Context-aware assistance
Learn more: GitHub Copilot CLI
Compile TypeScript to JavaScript:
pnpm run buildThe compiled output will be in the dist/ directory with the following structure:
dist/
βββ index.js
βββ server.js
βββ routes/
β βββ analyze.js
βββ domain/
βββ payload.js
βββ mapping.js
# Build the application
pnpm run build
# Start the server (uses compiled JavaScript)
pnpm run start- Set
HMAC_SECRETto a secure random value - Configure
RATE_LIMIT_BYPASS_TOKENif needed - Set
NODE_ENV=production - Use a process manager (PM2, systemd, Docker)
- Set up monitoring and logging
- Configure reverse proxy (nginx, Caddy)
- Enable HTTPS
- Set up automated backups
# Example Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && corepack prepare pnpm@9 --activate
RUN pnpm install --frozen-lockfile --prod
COPY . .
RUN pnpm run build
EXPOSE 8080
CMD ["pnpm", "run", "start"]# Run tests in watch mode (development)
pnpm test
# Run tests once (CI/CD)
pnpm test -- --run
# Run specific test file
pnpm test test/hmac.test.ts
# Run with coverage report
pnpm test -- --coverage
# Run tests in UI mode (interactive)
pnpm test -- --ui| Test File | Tests | Coverage Area |
|---|---|---|
test/hmac.test.ts |
4 | HMAC signature verification |
test/mapping.test.ts |
8 | Data mapping functions |
test/payload.test.ts |
21 | Zod schema validation |
test/analyze.integration.test.ts |
8 | API endpoint integration |
| Total | 41+ | All critical paths |
- Line Coverage: > 60%
- Branch Coverage: > 50%
- Function Coverage: > 70%
Check code style and potential errors:
pnpm run lintESLint is configured with:
eslint:recommended@typescript-eslint/recommended- TypeScript-aware rules
Run TypeScript type checker:
pnpm run typecheckConfiguration:
- Strict Mode: Enabled
- Target: ES2020
- Module: NodeNext (native ESM)
Before committing, run all checks:
pnpm run build && pnpm run lint && pnpm run typecheck && pnpm test -- --runProcesses plant photo analysis jobs with AI-powered insights.
Authentication: HMAC-SHA256 signature via x-signature header
Headers:
Content-Type: application/json
x-signature: <HMAC-SHA256 hex digest of request body>
x-rate-limit-bypass: <optional bypass token>Request Body:
{
"action": "analyze_photos",
"source": "Grow Photos",
"idempotency_scope": "photo_page_url+date",
"requested_fields_out": ["AI Summary", "Health 0-100"],
"jobs": [
{
"photo_page_url": "https://notion.so/photo-page-id",
"photo_file_urls": ["https://example.com/photo.jpg"],
"photo_title": "Plant Top View",
"date": "2024-01-01",
"angle": "top",
"plant_id": "BLUE",
"log_entry_url": "https://notion.so/log-entry-id",
"stage": "vegetative",
"room_name": "Grow Room A",
"fixture": "LED 1000W",
"photoperiod_h": 18,
"notes": "Looking healthy"
}
]
}Response (200 OK):
{
"results": [
{
"photo_page_url": "https://notion.so/photo-page-id",
"status": "ok",
"writebacks": {
"AI Summary": "Healthy canopy, RH slightly low for stage.",
"Health 0-100": 88,
"AI Next Step": "Raise light",
"VPD OK": true,
"DLI OK": false,
"CO2 OK": true,
"Trend": "Improving",
"DLI mol": 36.7,
"VPD kPa": 1.38,
"Sev": "Low"
}
}
],
"errors": []
}Error Response (400 Bad Request):
{
"error": {
"formErrors": [],
"fieldErrors": {
"jobs": ["Array must contain at least 1 element(s)"]
}
}
}Status Codes:
200 OK: Request processed successfully (may contain errors per job)400 Bad Request: Invalid request body or schema validation failure401 Unauthorized: Invalid or missing HMAC signature429 Too Many Requests: Rate limit exceeded (100 requests per minute per IP)
| Field | Type | Required | Description |
|---|---|---|---|
photo_page_url |
URL string | Yes | Notion page URL for the photo |
photo_file_urls |
URL array | Yes | Array of photo file URLs (min 1) |
photo_title |
string | No | Title of the photo |
date |
string | Yes | Date in YYYY-MM-DD format |
angle |
enum | No | Photo angle: top, close, under-canopy, trichomes, canopy, bud-site, full-plant, deficiency, tent, stem, roots, other |
plant_id |
enum | No | Plant identifier: BLUE, GREEN, OUTDOOR-A, OUTDOOR-B |
log_entry_url |
URL string | No | Related log entry URL |
stage |
string | No | Growth stage (e.g., "vegetative", "flowering") |
room_name |
string | No | Name of the grow room |
fixture |
string | No | Light fixture description |
photoperiod_h |
number | No | Light hours per day |
notes |
string | No | Additional notes |
Analysis results that can be written back to Notion:
| Field | Type | Description |
|---|---|---|
AI Summary |
string | Text summary of plant health |
Health 0-100 |
number (0-100) | Numeric health score |
AI Next Step |
enum/string | Recommended action: None, RH up, RH down, Dim, Raise light, Feed, Flush, IPM, Defol, Stake |
VPD OK |
boolean | Vapor Pressure Deficit status |
DLI OK |
boolean | Daily Light Integral status |
CO2 OK |
boolean | CO2 level status |
Trend |
enum | Health trend: Improving, Stable, Declining |
DLI mol |
number | DLI measurement in mol/mΒ²/day |
VPD kPa |
number | VPD measurement in kPa |
Sev |
enum | Severity level: Low, Medium, High, Critical |
The x-signature header must contain a HMAC-SHA256 hex digest of the request body:
Node.js Example:
const crypto = require('crypto');
const body = JSON.stringify(requestData);
const signature = crypto
.createHmac('sha256', process.env.HMAC_SECRET)
.update(body)
.digest('hex');
// Add to headers
headers['x-signature'] = signature;Python Example:
import hmac
import hashlib
import json
import os
body = json.dumps(request_data)
signature = hmac.new(
os.environ['HMAC_SECRET'].encode(),
body.encode(),
hashlib.sha256
).hexdigest()
# Add to headers
headers['x-signature'] = signaturecURL Example:
#!/bin/bash
SECRET="your-hmac-secret"
PAYLOAD='{"action":"analyze_photos","source":"Grow Photos","idempotency_scope":"photo_page_url+date","requested_fields_out":[],"jobs":[{"photo_page_url":"https://notion.so/photo","photo_file_urls":["https://example.com/photo.jpg"],"date":"2024-01-01"}]}'
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
curl -X POST http://localhost:8080/analyze \
-H "Content-Type: application/json" \
-H "x-signature: $SIGNATURE" \
-d "$PAYLOAD"The API implements IP-based rate limiting to protect against abuse:
Default Limits:
- 100 requests per minute per IP address
- 60-second sliding window
- Automatic cleanup of expired entries
Bypassing Rate Limits:
For trusted upstream services, you can bypass rate limiting:
curl -X POST http://localhost:8080/analyze \
-H "Content-Type: application/json" \
-H "x-signature: $SIGNATURE" \
-H "x-rate-limit-bypass: your-bypass-token" \
-d "$PAYLOAD"The bypass token must match the RATE_LIMIT_BYPASS_TOKEN environment variable.
Rate Limit Response (429 Too Many Requests):
{
"error": "Too Many Requests"
}Future Enhancements:
- Redis-based distributed rate limiting
- Per-endpoint rate limits
- Rate limit headers (X-RateLimit-Remaining, X-RateLimit-Reset)
All responses include comprehensive security headers:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: no-referrer
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'These headers protect against:
- XSS attacks: Content-Type sniffing, cross-site scripting
- Clickjacking: Frame embedding prevention
- MITM attacks: HSTS enforcement
- Information leakage: Referrer policy
- Unauthorized access: Permissions policy
src/
βββ index.ts # Application entry point
βββ server.ts # Fastify server setup
βββ routes/ # API route handlers
β βββ analyze.ts # POST /analyze endpoint
βββ domain/ # Business logic and schemas
βββ payload.ts # Zod schemas for request/response
βββ mapping.ts # Data transformation logic
test/
βββ hmac.test.ts # HMAC verification tests
βββ mapping.test.ts # Mapping function tests
βββ payload.test.ts # Schema validation tests
βββ analyze.integration.test.ts # API integration tests
docs/
βββ CONTRIBUTING.md # Contribution guidelines
βββ ARCHITECTURE.md # System architecture documentation
βββ CHANGELOG.md # Project changelog
This project is currently in active development. The following features are completed, in progress, or planned:
- Webhook endpoint with HMAC-SHA256 verification
- Request/response validation with Zod schemas
- Comprehensive test suite (41+ tests)
- Security middleware with rate limiting
- CI/CD pipeline with GitHub Actions
- Full TypeScript support with strict mode
- Development environment with hot reload
- Production build configuration
- Complete API documentation
- Architecture and contribution guidelines
- Notion API client integration
- Vision AI provider integration for photo analysis
- File download and processing logic
- Redis-based distributed rate limiting
- Background job queue with retry logic
- Monitoring and observability (metrics, traces, logs)
- Docker and Kubernetes deployment configs
- Horizontal scaling support
- Advanced analytics and trend analysis
- HMAC Verification: All webhook requests require HMAC-SHA256 signature verification
- Timing-Safe Comparison: Prevents timing attacks on signature verification
- Rate Limiting: IP-based rate limiting (100 req/min with bypass token support)
- Security Headers: Comprehensive security headers on all responses
- Input Validation: Runtime type checking with Zod schemas
- Environment Variables: Secrets stored in environment variables, never committed
- Error Sanitization: Error messages sanitized to prevent information leakage
- Type Safety: TypeScript strict mode prevents common vulnerabilities
- Generate Strong Secrets: Use
openssl rand -hex 32for HMAC secrets - Rotate Secrets Regularly: Update HMAC_SECRET and RATE_LIMIT_BYPASS_TOKEN periodically
- Use HTTPS: Always use HTTPS in production (enforced by HSTS header)
- Monitor Logs: Watch for repeated 401/429 responses indicating potential attacks
- Update Dependencies: Keep dependencies up to date with
pnpm update - Review Code: Use code review process for all changes
- Run Security Scans: Use npm audit or similar tools regularly
If you discover a security vulnerability, please email the maintainers directly. Do not open a public issue.
We welcome contributions! Please see CONTRIBUTING.md for detailed guidelines.
- Fork the repository
- Clone your fork:
git clone https://github.com/YOUR-USERNAME/Notion-Grow-Ops.git - Create a branch:
git checkout -b feature/my-feature - Make changes following our coding standards
- Add tests for new features
- Run checks:
pnpm run build && pnpm run lint && pnpm run typecheck && pnpm test -- --run - Commit with conventional commits:
git commit -m "feat: add new feature" - Push:
git push origin feature/my-feature - Open a Pull Request with a clear description
We use Conventional Commits for clear, structured commit messages:
feat(scope): add new feature
fix(scope): fix a bug
docs: update documentation
test: add or update tests
refactor: refactor code
chore: maintenance tasks
ci: CI/CD changes
- Architecture Guide - System design and component documentation
- Changelog - Project history and release notes
- Contributing Guide - Detailed contribution guidelines
This project is private and proprietary.
- Documentation: Start with this README, ARCHITECTURE.md, and CONTRIBUTING.md
- Issues: Open an issue on GitHub for bugs or feature requests
- Discussions: Use GitHub Discussions for questions and ideas
When opening an issue, please include:
Bug Reports:
- Description of the issue
- Steps to reproduce
- Expected vs actual behavior
- Environment details (Node version, OS, etc.)
- Relevant logs or error messages
Feature Requests:
- Clear description of the feature
- Use case and motivation
- Proposed implementation (optional)
- Potential alternatives considered
Built with:
- Fastify - Fast and low overhead web framework
- TypeScript - JavaScript with syntax for types
- Zod - TypeScript-first schema validation
- Vitest - Blazing fast unit test framework
- pnpm - Fast, disk space efficient package manager
- Language: TypeScript
- Framework: Fastify
- Test Coverage: >60%
- Tests: 41+
- Dependencies: Minimal and well-maintained
- License: Private
Made with β€οΈ for the grow operations community