Code Snippets - Mad Monkey Project

Project: "Where Has Benny Been?" - Command Examples & Scripts

Jump to: n8n Export/Import PostgreSQL Webhook Testing Shlink API Network/Port Scanning Node.js Setup Email/DNS

n8n Workflow Export & Import

Export All Workflows

BASH

Export all n8n workflows to JSON files for backup/version control

# Using n8n CLI (if installed)
n8n export:workflow --all --output=./workflows/

# Or via API
curl -u "admin:password" \
  "http://10.0.0.250:5678/api/v1/workflows" \
  | jq '.data[] | {name: .name, id: .id}' > workflows-list.json

# Export individual workflow
curl -u "admin:password" \
  "http://10.0.0.250:5678/api/v1/workflows/1" \
  > workflow-scan-intake.json

Import Workflow from JSON

BASH
# Via n8n CLI
n8n import:workflow --input=./workflow-scan-intake.json

# Via API
curl -X POST "http://10.0.0.250:5678/api/v1/workflows" \
  -u "admin:password" \
  -H "Content-Type: application/json" \
  -d @workflow-scan-intake.json

Activate/Deactivate Workflow

BASH
# Activate workflow
curl -X PATCH "http://10.0.0.250:5678/api/v1/workflows/1" \
  -u "admin:password" \
  -H "Content-Type: application/json" \
  -d '{"active": true}'

# Deactivate workflow
curl -X PATCH "http://10.0.0.250:5678/api/v1/workflows/1" \
  -u "admin:password" \
  -H "Content-Type: application/json" \
  -d '{"active": false}'

PostgreSQL Commands

Connect to Database

BASH
# Connect to PostgreSQL container on bennybeen (10.0.0.250)
docker exec -it mm-postgres psql -U mm_user -d madmonkey

# Or from host (if port exposed)
psql -h 10.0.0.250 -U mm_user -d madmonkey

# Connection string format
postgresql://mm_user:PASSWORD@10.0.0.250:5432/madmonkey

Create Database and Tables

SQL
-- Create database
CREATE DATABASE madmonkey;

-- Connect to database
\c madmonkey

-- Create cards table
CREATE TABLE cards (
    qr_id VARCHAR(10) PRIMARY KEY,
    type VARCHAR(10) NOT NULL,
    status VARCHAR(20) DEFAULT 'active',
    current_slug VARCHAR(50),
    move_code VARCHAR(20) UNIQUE,
    last_scan_ts TIMESTAMP,
    last_scan_city VARCHAR(100),
    total_scans INTEGER DEFAULT 0,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Add indexes
CREATE INDEX idx_cards_type ON cards(type);
CREATE INDEX idx_cards_status ON cards(status);
CREATE INDEX idx_cards_last_scan_ts ON cards(last_scan_ts);

Common Queries

SQL
-- Get all verified entries for current month
SELECT * FROM entries
WHERE verified = true
  AND month_bucket = TO_CHAR(NOW(), 'YYYY-MM')
ORDER BY created_at DESC;

-- Get top 10 most scanned cards
SELECT qr_id, type, total_scans, last_scan_city
FROM cards
ORDER BY total_scans DESC
LIMIT 10;

-- Find stuck cards (no scans in 10+ days)
SELECT qr_id, type, last_scan_ts, last_scan_city
FROM cards
WHERE last_scan_ts < NOW() - INTERVAL '10 days'
  AND status = 'active'
ORDER BY last_scan_ts ASC;

-- Get monthly contest entries by city
SELECT user_city, COUNT(*) as entries
FROM entries
WHERE verified = true
  AND month_bucket = '2024-12'
GROUP BY user_city
ORDER BY entries DESC;

-- Select random winner for the month
SELECT entry_id, email, name, qr_id
FROM entries
WHERE verified = true
  AND month_bucket = '2024-12'
ORDER BY RANDOM()
LIMIT 1;

Database Backup & Restore

BASH
# Backup database
docker exec mm-postgres pg_dump -U mm_user madmonkey > backup-$(date +%Y%m%d).sql

# Or with compression
docker exec mm-postgres pg_dump -U mm_user madmonkey | gzip > backup-$(date +%Y%m%d).sql.gz

# Restore database
docker exec -i mm-postgres psql -U mm_user madmonkey < backup-20241201.sql

# Restore from compressed backup
gunzip -c backup-20241201.sql.gz | docker exec -i mm-postgres psql -U mm_user madmonkey

Insert Test Data

SQL
-- Insert test cards
INSERT INTO cards (qr_id, type, status, move_code) VALUES
    ('N001', 'nest', 'active', NULL),
    ('N002', 'nest', 'active', NULL),
    ('T001', 'traveler', 'active', 'MOVE-T001-42XY'),
    ('T002', 'traveler', 'active', 'MOVE-T002-98ZW');

-- Insert test scan
INSERT INTO scans (qr_id, ip_anon, user_agent, geo_city, geo_region, geo_country)
VALUES ('T001', '203.0.113.0', 'Mozilla/5.0...', 'Chicago', 'Illinois', 'US');

-- Insert test entry
INSERT INTO entries (qr_id, email, name, user_city, verified, month_bucket)
VALUES ('T001', 'test@example.com', 'John Doe', 'Chicago, IL', true, '2024-12');

Webhook Testing with cURL

Test Scan Webhook

BASH
# Simple GET request (simulates QR scan)
curl -L "https://hooks.mad-monkey-creations.com/scan?qr_id=T42"

# With custom headers (simulate mobile device)
curl -L "https://hooks.mad-monkey-creations.com/scan?qr_id=T42" \
  -H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15" \
  -H "X-Forwarded-For: 203.0.113.42"

# Follow redirects and show headers
curl -L -v "https://hooks.mad-monkey-creations.com/scan?qr_id=T42"

Test Entry Submission Webhook

BASH
# Submit test entry
curl -X POST "https://hooks.mad-monkey-creations.com/entry" \
  -H "Content-Type: application/json" \
  -d '{
    "qr_id": "T42",
    "email": "test@example.com",
    "name": "John Doe",
    "user_city": "Chicago, IL",
    "consent": true
  }'

# Submit with verbose output
curl -v -X POST "https://hooks.mad-monkey-creations.com/entry" \
  -H "Content-Type: application/json" \
  -d @test-entry.json

# test-entry.json content:
cat > test-entry.json << EOF
{
    "qr_id": "T42",
    "email": "test@example.com",
    "name": "Test User",
    "user_city": "Chicago, IL",
    "consent": true
}
EOF

Test Listmonk Confirmation Webhook

BASH
# Simulate Listmonk confirmation webhook
curl -X POST "https://hooks.mad-monkey-creations.com/listmonk/confirm" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "subscriber.confirmed",
    "subscriber": {
        "id": 123,
        "email": "test@example.com",
        "name": "John Doe",
        "status": "enabled",
        "lists": [1]
    }
  }'

Create Short URLs

BASH
# Create single short URL (Shlink on bennybeen @ 10.0.0.250:8081)
curl -X POST "http://10.0.0.250:8081/rest/v3/short-urls" \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "longUrl": "https://hooks.mad-monkey-creations.com/scan?qr_id=T42",
    "customSlug": "T42",
    "domain": "mmlnk.us",
    "tags": ["benny", "traveler"]
  }'

# Bulk create short URLs (using loop)
for i in {1..10}; do
  QR_ID=$(printf "T%03d" $i)
  curl -X POST "http://10.0.0.250:8081/rest/v3/short-urls" \
    -H "X-Api-Key: your-api-key" \
    -H "Content-Type: application/json" \
    -d "{
      \"longUrl\": \"https://hooks.mad-monkey-creations.com/scan?qr_id=$QR_ID\",
      \"customSlug\": \"$QR_ID\",
      \"domain\": \"mmlnk.us\",
      \"tags\": [\"benny\", \"traveler\"]
    }"
  echo "Created: https://mmlnk.us/$QR_ID"
done

List and Search Short URLs

BASH
# List all short URLs
curl "http://10.0.0.250:8081/rest/v3/short-urls" \
  -H "X-Api-Key: your-api-key"

# Search by tag
curl "http://10.0.0.250:8081/rest/v3/short-urls?tags[]=traveler" \
  -H "X-Api-Key: your-api-key"

# Paginated results
curl "http://10.0.0.250:8081/rest/v3/short-urls?page=1&itemsPerPage=50" \
  -H "X-Api-Key: your-api-key"

# Search by term
curl "http://10.0.0.250:8081/rest/v3/short-urls?searchTerm=T42" \
  -H "X-Api-Key: your-api-key"

Get Visit Statistics

BASH
# Get visits for specific short URL
curl "http://10.0.0.250:8081/rest/v3/short-urls/T42/visits" \
  -H "X-Api-Key: your-api-key"

# Get visits with date range
curl "http://10.0.0.250:8081/rest/v3/short-urls/T42/visits?startDate=2024-12-01&endDate=2024-12-31" \
  -H "X-Api-Key: your-api-key"

# Get aggregated stats
curl "http://10.0.0.250:8081/rest/v3/short-urls/T42" \
  -H "X-Api-Key: your-api-key" \
  | jq '.visitsCount'

Generate Shlink API Key

BASH
# Generate API key using Shlink CLI in container
docker exec mm-shlink shlink api-key:generate

# Generate with specific permissions
docker exec mm-shlink shlink api-key:generate \
  --name="n8n-integration" \
  --role-name="Domain"

# List all API keys
docker exec mm-shlink shlink api-key:list

Network & Port Scanning

Check Port Status

BASH
# Check if port is open (telnet) - All services on bennybeen (10.0.0.250)
telnet 10.0.0.250 5432

# Check with netcat
nc -zv 10.0.0.250 5432

# Check multiple ports on Docker host
for port in 5432 5678 8080 8081 9000; do
  nc -zv -w 2 10.0.0.250 $port
done

# Use nmap for comprehensive scan
nmap -p 5432,5678,8080,8081,9000 10.0.0.250

Test Mail Ports

BASH
# Test SMTP submission port 587 (ForwardMail.net relay)
openssl s_client -starttls smtp -connect smtp.forwardemail.net:587

# Test ForwardMail.net SMTP
telnet smtp.forwardemail.net 587

# Full service port scan on Docker host (external IP is DYNAMIC: 76.150.65.61)
nmap -sV -p 80,443 76.150.65.61

# NOTE: Email is handled by ForwardMail.net, not local mail server

Check Service Availability

BASH
# Check if PostgreSQL is accepting connections (all services on 10.0.0.250)
pg_isready -h 10.0.0.250 -p 5432 -U mm_user

# Check HTTP services on bennybeen
curl -I http://10.0.0.250:8081  # Shlink
curl -I http://10.0.0.250:9000  # Listmonk

# Check with timeout
timeout 5 curl -I http://10.0.0.250:5678 || echo "n8n unavailable"

# Check all Mad Monkey services on single Docker host
for service in "10.0.0.250:5432" "10.0.0.250:5678" "10.0.0.250:8080" "10.0.0.250:8081" "10.0.0.250:9000"; do
  echo "Checking $service..."
  nc -zv -w 2 ${service/:/ } && echo "✓ OK" || echo "✗ FAILED"
done

Node.js Setup

Install Node.js via NVM

BASH
# Install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# Load NVM
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# Install Node.js 24.12.0
nvm install 24.12.0
nvm use 24.12.0
nvm alias default 24.12.0

# Verify installation
node --version  # v24.12.0
npm --version   # 9.2.0

# Make available system-wide (create symlink)
sudo ln -s "$NVM_DIR/versions/node/$(nvm version)/bin/node" /usr/local/bin/node
sudo ln -s "$NVM_DIR/versions/node/$(nvm version)/bin/npm" /usr/local/bin/npm

Install QR Code Generator

BASH
# Install mini-qr globally
npm install -g mini-qr

# Or install locally in project
mkdir qr-generator && cd qr-generator
npm init -y
npm install mini-qr

# Generate QR code
mini-qr "https://mmlnk.us/T42" -o T42.png

# Generate SVG
mini-qr "https://mmlnk.us/T42" -o T42.svg -t svg

# Generate multiple QR codes
for i in {1..10}; do
  QR_ID=$(printf "T%03d" $i)
  mini-qr "https://mmlnk.us/$QR_ID" -o "qr-codes/${QR_ID}.png"
  echo "Generated: ${QR_ID}.png"
done

QR Code Generation Script

JAVASCRIPT
// generate-qr-codes.js
const QRCode = require('mini-qr');
const fs = require('fs');
const path = require('path');

// Configuration
const BASE_URL = 'https://mmlnk.us';
const OUTPUT_DIR = './qr-codes';
const CARD_TYPES = {
  nest: { prefix: 'N', count: 300 },
  traveler: { prefix: 'T', count: 100 }
};

// Create output directory
if (!fs.existsSync(OUTPUT_DIR)) {
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}

// Generate QR codes
for (const [type, config] of Object.entries(CARD_TYPES)) {
  console.log(`Generating ${config.count} ${type} cards...`);

  for (let i = 1; i <= config.count; i++) {
    const qrId = `${config.prefix}${String(i).padStart(3, '0')}`;
    const url = `${BASE_URL}/${qrId}`;
    const filename = path.join(OUTPUT_DIR, `${qrId}.png`);

    const qr = new QRCode(url);
    qr.toFile(filename);

    if (i % 50 === 0) {
      console.log(`  Generated ${i}/${config.count}...`);
    }
  }

  console.log(`✓ Completed ${type} cards\n`);
}

console.log('All QR codes generated successfully!');
Usage: node generate-qr-codes.js

Email & DNS Verification

Check DNS Records

BASH
# Check A record
dig mmlnk.us A +short
dig mad-monkey-creations.com A +short

# Check MX record
dig mad-monkey-creations.com MX +short

# Check TXT records (SPF, DKIM, DMARC)
dig mad-monkey-creations.com TXT +short
dig _dmarc.mad-monkey-creations.com TXT +short
dig default._domainkey.mad-monkey-creations.com TXT +short

# Use nslookup (alternative)
nslookup -type=MX mad-monkey-creations.com
nslookup -type=TXT mad-monkey-creations.com

# Check DNS propagation
dig @8.8.8.8 mmlnk.us  # Google DNS
dig @1.1.1.1 mmlnk.us  # Cloudflare DNS

Test SMTP Connection

BASH
# Test SMTP with openssl (ForwardMail.net - ONLY email provider)
openssl s_client -connect smtp.forwardemail.net:587 -starttls smtp

# Send test email via command line
cat > test-email.txt << EOF
From: hello@mad-monkey-creations.com
To: test@example.com
Subject: Test Email

This is a test email from Mad Monkey Creations.
EOF

# Send via SMTP (requires mailx or similar)
cat test-email.txt | sendmail -v test@example.com

Configure Postfix for SMTP Relay

BASH
# Install Postfix
apt-get update && apt-get install -y postfix

# Configure main.cf
cat >> /etc/postfix/main.cf << EOF
# SMTP Relay Configuration
relayhost = [smtp.forwardmail.net]:587
smtp_use_tls = yes
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
EOF

# Create SASL password file
cat > /etc/postfix/sasl_passwd << EOF
[smtp.forwardmail.net]:587 username:password
EOF

# Secure and hash the file
chmod 600 /etc/postfix/sasl_passwd
postmap /etc/postfix/sasl_passwd

# Restart Postfix
systemctl restart postfix

# Test
echo "Test email" | mail -s "Test Subject" test@example.com

Verify Email Deliverability

BASH
# Check SPF record
dig mad-monkey-creations.com TXT | grep spf

# Check DMARC record
dig _dmarc.mad-monkey-creations.com TXT

# Check DKIM record
dig default._domainkey.mad-monkey-creations.com TXT

# Use online tools (open in browser)
# https://mxtoolbox.com/SuperTool.aspx
# https://www.mail-tester.com/
# https://dkimvalidator.com/

# Command-line DKIM check
curl -X POST "https://dkimvalidator.com/api" \
  -d "email=test@mad-monkey-creations.com"

Docker Container Management

BASH
# Start all Mad Monkey containers
docker-compose -f docker-compose-postgres.yml up -d
docker-compose -f docker-compose-shlink.yml up -d
docker-compose -f docker-compose-listmonk.yml up -d

# Check container status
docker ps | grep mm-

# View logs
docker logs -f mm-postgres
docker logs -f mm-shlink --tail 100
docker logs -f mm-listmonk

# Restart containers
docker restart mm-postgres mm-shlink mm-listmonk

# Stop all containers
docker stop mm-postgres mm-shlink mm-listmonk

# Remove containers (data persists in volumes)
docker rm mm-postgres mm-shlink mm-listmonk

# View volumes
docker volume ls | grep mm

# Backup volume
docker run --rm -v postgres_data:/data -v $(pwd):/backup \
  ubuntu tar czf /backup/postgres-backup-$(date +%Y%m%d).tar.gz /data

Environment Variables Template

BASH
# .env file for Mad Monkey project
# Copy to .env and fill in actual values
# All services run on single Docker host: bennybeen (10.0.0.250)

# Server Configuration
SERVER_HOST=10.0.0.250
EXTERNAL_IP=76.150.65.61  # DYNAMIC - can change!
PROJECT_PATH=/home/bennybeen/mad-monkey/

# PostgreSQL (on 10.0.0.250:5432)
POSTGRES_PASSWORD=YOUR_STRONG_PASSWORD_HERE
DB_HOST=10.0.0.250
DB_PORT=5432
DB_NAME=madmonkey
DB_USER=mm_user

# n8n (on 10.0.0.250:5678)
N8N_ENCRYPTION_KEY=GENERATE_RANDOM_KEY
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=YOUR_PASSWORD
N8N_URL=http://10.0.0.250:5678

# Listmonk (on 10.0.0.250:9000)
LISTMONK_URL=http://10.0.0.250:9000
LISTMONK_API_USER=admin
LISTMONK_API_PASSWORD=YOUR_PASSWORD
LISTMONK_LIST_ID=1

# Shlink (on 10.0.0.250:8081)
SHLINK_URL=http://10.0.0.250:8081
SHLINK_API_KEY=GENERATE_VIA_CLI
DEFAULT_DOMAIN=mmlnk.us

# Email (ForwardMail.net - ONLY provider)
SMTP_HOST=smtp.forwardemail.net
SMTP_PORT=587
SMTP_USER=hello@mad-monkey-creations.com
SMTP_PASS=YOUR_SMTP_PASSWORD
MM_SENDER_EMAIL=hello@mad-monkey-creations.com

# Application
MM_LANDING_BASE=https://www.mad-monkey-creations.com
MM_REWARD_URL=https://www.mad-monkey-creations.com/reward
MM_DISCOUNT_CODE=BANANAS10

# External APIs (optional)
IPAPI_KEY=
ETSY_API_KEY=
PRINTIFY_API_TOKEN=

# To load in shell:
# export $(cat .env | xargs)