Compare commits

..

No commits in common. "master" and "go" have entirely different histories.
master ... go

2778 changed files with 938 additions and 15220 deletions

10
.gitignore vendored
View file

@ -2,8 +2,6 @@ app/tmp/*
*.tar.gz
*.swp
*.swo
.env.prod
.env.stg
app/vendors/tcpdf/cache/*
app/tests/*
app/emails/*
@ -13,11 +11,3 @@ app/vaultmsgs/*
app/cake_eclipse_helper.php
app/webroot/pdf/*
app/webroot/attachments_files/*
backups/*
# Go binaries
go/server
go/vault
go/go.mod
go/go.sum
go/goose.env

View file

@ -6,8 +6,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
CMC Sales is a B2B sales management system for CMC Technologies. The codebase consists of:
- **Legacy CakePHP 1.2.5 application** (in `/php/`) - Primary business logic
- **Modern Go API** (in `/go/`) - New development using sqlc and Gorilla Mux
- **Legacy CakePHP 1.2.5 application** (2008-era) - Primary business logic
- **Modern Go API** (in `/go-app/`) - New development using sqlc and Gorilla Mux
**Note**: Documentation also references a Django application that is not present in the current codebase.
@ -42,7 +42,7 @@ gunzip < backups/backup_*.sql.gz | mariadb -h 127.0.0.1 -u cmc -p cmc
### Go Application Development
```bash
# Navigate to Go app directory
cd go
cd go-app
# Configure private module access (first time only)
go env -w GOPRIVATE=code.springupsoftware.com
@ -80,7 +80,7 @@ make build
### Go Application (Modern)
- **Framework**: Gorilla Mux (HTTP router)
- **Database**: sqlc for type-safe SQL queries
- **Location**: `/go/`
- **Location**: `/go-app/`
- **Structure**:
- `cmd/server/` - Main application entry point
- `internal/cmc/handlers/` - HTTP request handlers

View file

@ -57,9 +57,5 @@ RUN chmod +x /var/www/cmc-sales/run_vault.sh
RUN chmod +x /var/www/cmc-sales/run_update_invoices.sh
# Ensure Apache error/access logs go to Docker stdout/stderr
RUN ln -sf /dev/stdout /var/log/apache2/access.log && \
ln -sf /dev/stderr /var/log/apache2/error.log
# By default, simply start apache.
CMD /usr/sbin/apache2ctl -D FOREGROUND

View file

@ -22,14 +22,14 @@ RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
# Generate sqlc code
RUN sqlc generate
# Build the application with staging tags
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -tags staging -o server cmd/server/main.go
# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server cmd/server/main.go
# Runtime stage
FROM alpine:latest
# Install runtime dependencies and debugging tools for staging
RUN apk --no-cache add ca-certificates curl net-tools
# Install runtime dependencies
RUN apk --no-cache add ca-certificates
WORKDIR /root/
@ -40,18 +40,11 @@ COPY --from=builder /app/server .
COPY go-app/templates ./templates
COPY go-app/static ./static
# Copy staging environment file
COPY go-app/.env.staging .env
# Create credentials directory
RUN mkdir -p ./credentials
# Copy .env file if needed
COPY go-app/.env.example .env
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/api/v1/health || exit 1
# Run the application
CMD ["./server"]

View file

@ -1,27 +0,0 @@
FROM golang:1.24-alpine
WORKDIR /app
# Copy go.mod and go.sum first
COPY go/go.mod go/go.sum ./
# Download dependencies
RUN go mod download
# Install Air for hot reload (pinned to v1.52.3 for Go 1.24 compatibility)
RUN go install github.com/air-verse/air@v1.52.3
# Install sqlc for SQL code generation
RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
# Copy source code
COPY go/ .
# Generate sqlc code
RUN sqlc generate
# Copy Air config
COPY go/.air.toml .air.toml
EXPOSE 8080
CMD ["air", "-c", ".air.toml"]

View file

@ -1,47 +0,0 @@
# Use the official PHP 5.6 Apache image for classic mod_php
FROM php:5.6-apache
# Install required system libraries and PHP extensions for CakePHP
RUN sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list && \
sed -i 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' /etc/apt/sources.list && \
sed -i '/stretch-updates/d' /etc/apt/sources.list && \
echo 'Acquire::AllowInsecureRepositories "true";' > /etc/apt/apt.conf.d/99allow-insecure && \
echo 'Acquire::AllowDowngradeToInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99allow-insecure && \
apt-get update && \
apt-get install --allow-unauthenticated -y libc-client2007e-dev libkrb5-dev libpng-dev libjpeg-dev libfreetype6-dev libcurl4-openssl-dev libxml2-dev libssl-dev libmcrypt-dev libicu-dev && \
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
docker-php-ext-configure imap --with-kerberos --with-imap-ssl && \
docker-php-ext-install mysqli pdo pdo_mysql mbstring gd curl imap
# Set environment variables.
ENV HOME /root
# Define working directory.
WORKDIR /root
ARG COMMIT
ENV COMMIT_SHA=${COMMIT}
EXPOSE 80
# Copy vhost config to Apache's sites-available
ADD conf/apache-vhost.conf /etc/apache2/sites-available/cmc-sales.conf
ADD conf/ripmime /bin/ripmime
RUN chmod +x /bin/ripmime \
&& a2ensite cmc-sales \
&& a2dissite 000-default \
&& a2enmod rewrite \
&& a2enmod headers
# Copy site into place.
ADD php/ /var/www/cmc-sales
ADD php/app/config/database_local.php /var/www/cmc-sales/app/config/database.php
RUN mkdir -p /var/www/cmc-sales/app/tmp
RUN mkdir -p /var/www/cmc-sales/app/tmp/logs
RUN chmod -R 755 /var/www/cmc-sales/app/tmp
# Ensure CakePHP tmp directory is writable by web server
RUN chmod -R 777 /var/www/cmc-sales/app/tmp
# By default, simply start apache.
CMD /usr/sbin/apache2ctl -D FOREGROUND

View file

@ -1,4 +0,0 @@
FROM mariadb:latest
# Copy custom MariaDB configuration to disable strict mode
COPY conf/mariadb-no-strict.cnf /etc/mysql/conf.d/

View file

@ -1,23 +0,0 @@
FROM golang:1.24-alpine AS builder
RUN apk add --no-cache git
WORKDIR /app
COPY go/go.mod go/go.sum ./
RUN go mod download
COPY go/ .
RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
RUN sqlc generate
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server cmd/server/main.go
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o vault cmd/vault/main.go
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /root/
COPY --from=builder /app/server .
COPY --from=builder /app/vault .
COPY go/templates ./templates
COPY go/static ./static
COPY go/.env.example .env
EXPOSE 8082
CMD ["./server"]

View file

@ -1,47 +0,0 @@
# Use the official PHP 5.6 Apache image for classic mod_php
FROM php:5.6-apache
# Install required system libraries and PHP extensions for CakePHP
RUN sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list && \
sed -i 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' /etc/apt/sources.list && \
sed -i '/stretch-updates/d' /etc/apt/sources.list && \
echo 'Acquire::AllowInsecureRepositories "true";' > /etc/apt/apt.conf.d/99allow-insecure && \
echo 'Acquire::AllowDowngradeToInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99allow-insecure && \
apt-get update && \
apt-get install --allow-unauthenticated -y libc-client2007e-dev libkrb5-dev libpng-dev libjpeg-dev libfreetype6-dev libcurl4-openssl-dev libxml2-dev libssl-dev libmcrypt-dev libicu-dev && \
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
docker-php-ext-configure imap --with-kerberos --with-imap-ssl && \
docker-php-ext-install mysqli pdo pdo_mysql mbstring gd curl imap
# Set environment variables.
ENV HOME /root
# Define working directory.
WORKDIR /root
ARG COMMIT
ENV COMMIT_SHA=${COMMIT}
EXPOSE 80
# Copy vhost config to Apache's sites-available
ADD conf/apache-vhost.conf /etc/apache2/sites-available/cmc-sales.conf
ADD conf/ripmime /bin/ripmime
RUN chmod +x /bin/ripmime \
&& a2ensite cmc-sales \
&& a2dissite 000-default \
&& a2enmod rewrite \
&& a2enmod headers
# Copy site into place.
ADD php/ /var/www/cmc-sales
ADD php/app/config/database.php /var/www/cmc-sales/app/config/database.php
RUN mkdir -p /var/www/cmc-sales/app/tmp
RUN mkdir -p /var/www/cmc-sales/app/tmp/logs
RUN chmod -R 755 /var/www/cmc-sales/app/tmp
# Ensure CakePHP tmp directory is writable by web server
RUN chmod -R 777 /var/www/cmc-sales/app/tmp
# By default, simply start apache.
CMD /usr/sbin/apache2ctl -D FOREGROUND

View file

@ -1 +0,0 @@
FROM mariadb:latest

View file

@ -1,21 +0,0 @@
FROM golang:1.24-alpine AS builder
RUN apk add --no-cache git
WORKDIR /app
COPY go/go.mod go/go.sum ./
RUN go mod download
COPY go/ .
RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
RUN sqlc generate
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server cmd/server/main.go
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /root/
COPY --from=builder /app/server .
COPY go/templates ./templates
COPY go/static ./static
COPY go/.env.example .env
EXPOSE 8082
CMD ["./server"]

View file

@ -1,67 +0,0 @@
# Use the official PHP 5.6 Apache image for classic mod_php
FROM php:5.6-apache
# Install required system libraries and PHP extensions for CakePHP
RUN sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list && \
sed -i 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' /etc/apt/sources.list && \
sed -i '/stretch-updates/d' /etc/apt/sources.list && \
echo 'Acquire::AllowInsecureRepositories "true";' > /etc/apt/apt.conf.d/99allow-insecure && \
echo 'Acquire::AllowDowngradeToInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99allow-insecure && \
apt-get update && \
apt-get install --allow-unauthenticated -y libc-client2007e-dev libkrb5-dev libpng-dev libjpeg-dev libfreetype6-dev libcurl4-openssl-dev libxml2-dev libssl-dev libmcrypt-dev libicu-dev && \
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
docker-php-ext-configure imap --with-kerberos --with-imap-ssl && \
docker-php-ext-install mysqli pdo pdo_mysql mbstring gd curl imap
# Set environment variables.
ENV HOME /root
# Define working directory.
WORKDIR /root
ARG COMMIT
ENV COMMIT_SHA=${COMMIT}
EXPOSE 80
# Legacy apt compatibility and install steps for Ubuntu 16.04 (now commented out)
# RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://old-releases.ubuntu.com/ubuntu/|g' /etc/apt/sources.list && \
# sed -i 's|http://security.ubuntu.com/ubuntu|http://old-releases.ubuntu.com/ubuntu|g' /etc/apt/sources.list
# RUN apt-get update
# RUN apt-get -y upgrade
# RUN echo 'Acquire::AllowInsecureRepositories "true";' > /etc/apt/apt.conf.d/99allow-insecure
# RUN apt-get update -o Acquire::AllowInsecureRepositories=true --allow-unauthenticated
# RUN DEBIAN_FRONTEND=noninteractive apt-get -y install apache2 libapache2-mod-php5 php5-mysql php5-gd php-pear php-apc php5-curl php5-imap
# RUN a2enmod php5
# RUN php5enmod openssl
# RUN sed -i "s/short_open_tag = Off/short_open_tag = On/" /etc/php5/apache2/php.ini
# RUN sed -i "s/error_reporting = .*$/error_reporting = E_ERROR | E_WARNING | E_PARSE/" /etc/php5/apache2/php.ini
# ADD conf/php.ini /etc/php5/apache2/php.ini
# Copy vhost config to Apache's sites-available
ADD conf/apache-vhost.conf /etc/apache2/sites-available/cmc-sales.conf
ADD conf/ripmime /bin/ripmime
RUN chmod +x /bin/ripmime \
&& a2ensite cmc-sales \
&& a2dissite 000-default \
&& a2enmod rewrite \
&& a2enmod headers
# Copy site into place.
ADD php/ /var/www/cmc-sales
ADD php/app/config/database_stg.php /var/www/cmc-sales/app/config/database.php
RUN mkdir -p /var/www/cmc-sales/app/tmp
RUN mkdir -p /var/www/cmc-sales/app/tmp/logs
RUN chmod -R 755 /var/www/cmc-sales/app/tmp
# Ensure CakePHP tmp directory is writable by web server
RUN chmod -R 777 /var/www/cmc-sales/app/tmp
# No need to disable proxy_fcgi or remove PHP-FPM conf files in this image
# By default, simply start apache.
CMD /usr/sbin/apache2ctl -D FOREGROUND

63
Dockerfile_stg Normal file
View file

@ -0,0 +1,63 @@
# This is 99% the same as the prod one. I should do something smarter here.
FROM ubuntu:lucid
# Set environment variables.
ENV HOME /root
# Define working directory.
WORKDIR /root
RUN sed -i 's/archive/old-releases/' /etc/apt/sources.list
RUN apt-get update
RUN apt-get -y upgrade
# Install apache, PHP, and supplimentary programs. curl and lynx-cur are for debugging the container.
RUN DEBIAN_FRONTEND=noninteractive apt-get -y install apache2 libapache2-mod-php5 php5-mysql php5-gd php-pear php-apc php5-curl php5-imap
# Enable apache mods.
#RUN php5enmod openssl
RUN a2enmod php5
RUN a2enmod rewrite
RUN a2enmod headers
# Update the PHP.ini file, enable <? ?> tags and quieten logging.
# RUN sed -i "s/short_open_tag = Off/short_open_tag = On/" /etc/php5/apache2/php.ini
#RUN sed -i "s/error_reporting = .*$/error_reporting = E_ERROR | E_WARNING | E_PARSE/" /etc/php5/apache2/php.ini
ADD conf/php.ini /etc/php5/apache2/php.ini
# Manually set up the apache environment variables
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2
ENV APACHE_PID_FILE /var/run/apache2.pid
ARG COMMIT
ENV COMMIT_SHA=${COMMIT}
EXPOSE 80
# Update the default apache site with the config we created.
ADD conf/apache-vhost.conf /etc/apache2/sites-available/cmc-sales
ADD conf/ripmime /bin/ripmime
RUN chmod +x /bin/ripmime
RUN a2dissite 000-default
# Copy site into place.
ADD . /var/www/cmc-sales
ADD app/config/database_stg.php /var/www/cmc-sales/app/config/database.php
RUN mkdir /var/www/cmc-sales/app/tmp
RUN mkdir /var/www/cmc-sales/app/tmp/logs
RUN chmod -R 755 /var/www/cmc-sales/app/tmp
RUN chmod +x /var/www/cmc-sales/run_vault.sh
RUN a2ensite cmc-sales
# By default, simply start apache.
CMD /usr/sbin/apache2ctl -D FOREGROUND

18
MIGRATION.md Normal file
View file

@ -0,0 +1,18 @@
# migration instructions
mysql -u cmc -p cmc < ~/migration/latest.sql
MariaDB [(none)]> CREATE USER 'cmc'@'172.17.0.2' IDENTIFIED BY 'somepass';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON cmc.* TO 'cmc'@'172.17.0.2';
www-data@helios:~$ du -hcs vaultmsgs
64G vaultmsgs
64G total
www-data@helios:~$ du -hcs emails
192G emails
192G total
www-data@helios:~$

138
README.md
View file

@ -1,23 +1,9 @@
# cmc-sales
CMC Sales is a business management system with two applications:
- **PHP Application**: CakePHP 1.2.5 (currently the primary application)
- **Go Application**: Go + HTMX (used for select features, growing)
**Future development should be done in the Go application wherever possible.**
## Architecture
Both applications:
- Share the same MariaDB database
- Run behind a shared Caddy reverse proxy with basic authentication
- Support staging and production environments on the same server
The PHP application currently handles most functionality, while the Go application is used for select screens and new features as they're developed.
## Development Setup
CMC Sales now runs both legacy CakePHP and modern Go applications side by side.
### Quick Start
``` shell
@ -59,53 +45,105 @@ Both applications share the same database, allowing for gradual migration.
- **Go Application**: Requires Go 1.23+ (for latest sqlc)
- Alternative: Use `Dockerfile.go.legacy` with Go 1.21 and sqlc v1.26.0
## Deployment
### Prerequisites
## Install a new server
The deployment scripts use SSH to connect to the server. Configure your SSH config (`~/.ssh/config`) with a host entry named `cmc` pointing to the correct server:
(TODO this is all likely out of date)
### Requirements
Debian or Ubuntu OS. These instructions written for Debian 9.9
Assumed pre-work:
Create a new VM with hostname newserver.cmctechnologies.com.au
Configure DNS appropriately. cmctechnologies.com.au zones is currently managed in Google Cloud DNS on Karl's account:
https://console.cloud.google.com/net-services/dns/zones/cmctechnologies?project=cmc-technologies&authuser=1&folder&organizationId
Will need to migrate that to CMC's GSuite account at some point.
1. Install ansible on your workstation
```
apt-get install ansible
```
2. Clone the playbooks
```
git clone git@gitlab.com:minimalist.software/cmc-playbooks.git
```
3. Execute the playbooks
The nginx config expects the site to be available at sales.cmctechnologies.com.au.
You'll need to add the hostname to config/nginx-site, if this isn't sales.cmctechnologies.com.au
```
Host cmc
HostName node0.prd.springupsoftware.com
User cmc
IdentityFile ~/.ssh/cmc
cd cmc-playbooks
# Add the hostname of your new server to the inventory.txt
ansible-playbook -i inventory.txt setup.yml
```
4. SSH to the new server and configure gitlab-runner
```
ssh newserver.cmctechnologies.com.au
sudo gitlab-runner register
```
5. SSH to the new server as cmc user
```
ssh cmc@newserver.cmctechnologies.com.au
```
### Deployment Procedures
Deploy to staging or production using the scripts in the `deploy/` directory:
**Deploy to Staging:**
```bash
./scripts/deploy/deploy-stg.sh
6. Add the SSH key to the cmc-sales repo on gitlab as a deploy key
https://gitlab.com/minimalist.software/cmc-sales/-/settings/repository
```
cmc@cmc:~$ cat .ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFIdoWVp2pGDb46ubW6jkfIpREMa/veD6xZVAtnj3WG1sX7NEUlQYq3RKbZ5CThlw6GKMSYoIsIqk7p6zSoJHGlJSLxoJ0edKflciMUFMTQrdm4T1USXsK+gd0C4DUCyVkYFOs37sy+JtziymnBTm7iOeVI3aMxwfoCOs6mNiD0ettjJT6WtVyy0ZTb6yU4uz7CHj1IGsvwsoKJWPGwJrZ/MfByNl6aJ8R/8zDwbtP06owKD4b3ZPgakM3nYRRoKzHZ/SClz50SXMKC4/nmFY9wLuuMhCWK+9x4/4VPSnxXESOlENMfUoa1IY4osAnZCtaFrWDyenJ+spZrNfgcscD ansible-generated on cmc
```
**Deploy to Production:**
```bash
./deploy/deploy-prod.sh
6. Clone the cmc-sales repo
```
git clone git@gitlab.com:minimalist.software/cmc-sales.git
```
**Rebuild without cache (useful after dependency changes):**
```bash
./scripts/deploy/deploy-prod.sh --no-cache
./scripts/deploy/deploy-stg.sh --no-cache
7. As root on new server configure mySQL user cmc
Note: get password from app/config/database.php
(or set a new one and change it)
```
# mysql -u root
CREATE USER 'cmc'@'localhost' IDENTIFIED BY 'password';
CREATE USER 'cmc'@'172.17.0.2' IDENTIFIED BY 'password';
CREATE database cmc;
GRANT ALL PRIVILEGES ON cmc.* TO 'cmc'@'localhost';
GRANT ALL PRIVILEGES ON cmc.* TO 'cmc'@'172.17.0.2';
```
### How Deployment Works
8. Get the latest backup from Google Drive
1. The deploy script connects to the server via the `cmc` SSH host
2. Clones or updates the appropriate git branch (`stg` or `prod`)
3. Creates environment configuration for the Go application
4. Builds and starts Docker containers using the appropriate compose file
5. Applications are accessible through Caddy reverse proxy with basic auth
In the shared google drive:
eg. backups/database/backup_20191217_21001.sql.gz
### Deployment Environments
Copy up to the new server:
```
rsync backup_*.gz root@newserver:~/
- **Staging**: Branch `stg` → https://stg.cmctechnologies.com.au
- **Production**: Branch `prod` → https://sales.cmctechnologies.com.au or https://prod.cmctechnologies.com.au
```
Both environments run on the same server and share:
- A single Caddy reverse proxy (handles HTTPS and basic authentication for both environments)
- Separate Docker containers for each environment's PHP and Go applications
- Separate MariaDB database instances
9. Restore backup to cmc database
```
zcat backup_* | mysql -u cmc -p
```
10. Redeploy from Gitlab
https://gitlab.com/minimalist.software/cmc-sales/pipelines/new
11. You should have a new installation of cmc-sales.
12. Seems new Linux kernels break the docker
https://github.com/moby/moby/issues/28705
13. Mysql needs special args not to break
```
# /etc/mysql/my.cnf
sql_mode=ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
```

View file

@ -43,6 +43,4 @@
*
*/
//EOF
require_once(dirname(__FILE__) . '/php7_compat.php');
?>

View file

@ -47,13 +47,19 @@ Configure::write('version', '1.0.1');
$host = $_SERVER['HTTP_HOST'];
// SMTP settings
Configure::write('smtp_settings', array(
'port' => '25',
'timeout' => '30',
'host' => 'postfix'
/*Configure::write('smtp_settings', array(
'port' => '587',
'timeout' => '60',
'host' => 'smtp-relay.gmail.com',
'username' => 'sales',
'password' => 'S%s\'mMZ})MGsg$k!5N|mPSQ>}'
));
*/
Configure::write('smtp_settings', array(
'port' => '25',
'timeout' => '30',
'host' => '172.17.0.1'));
//Production/Staging Config
@ -64,18 +70,18 @@ $production_hosts = array('cmc.lan', '192.168.0.7', 'cmcbeta.lan', 'office.cmcte
$basedir = '/var/www/cmc-sales/app/';
Cache::config('default', array(
'engine' => 'File', //[required]
'duration'=> 3600, //[optional]
'probability'=> 100, //[optional]
'path' => '/home/cmc/cmc-sales/app/tmp/', //[optional] use system tmp directory - remember to use absolute path
'prefix' => 'cake_', //[optional] prefix every cache file with this string
'lock' => false, //[optional] use file locking
'serialize' => true,
'engine' => 'File', //[required]
'duration'=> 3600, //[optional]
'probability'=> 100, //[optional]
'path' => '/home/cmc/cmc-sales/app/tmp/', //[optional] use system tmp directory - remember to use absolute path
'prefix' => 'cake_', //[optional] prefix every cache file with this string
'lock' => false, //[optional] use file locking
'serialize' => true,
));
Configure::write('email_directory', '/var/www/emails');
Configure::write('pdf_directory', $basedir.'webroot/pdf/');
Configure::write('attachments_directory', $basedir.'webroot/attachments_files/');
Configure::write('pdf_directory', $basedir.'/webroot/pdf/');
Configure::write('attachments_directory', $basedir.'/webroot/attachments_files/');
/**
@ -168,30 +174,6 @@ Configure::write('Security.salt', 'uiPxR3MzVXAID5zucbxLdxP4TX33buPoCWZr4JfroGoaE
Configure::write('Acl.classname', 'DbAcl');
Configure::write('Acl.database', 'default');
/**
* Tailscale Authentication Configuration
*
* Enable Tailscale HTTP header authentication support
* When enabled, the system will check for Tailscale authentication headers
* before falling back to HTTP Basic Auth
*/
Configure::write('Tailscale.enabled', true);
/**
* Auto-create users from Tailscale authentication
* When enabled, users authenticated via Tailscale headers will be
* automatically created if they don't exist in the database
*/
Configure::write('Tailscale.autoCreateUsers', false);
/**
* Default access level for auto-created Tailscale users
* Options: 'user', 'manager', 'admin'
*/
Configure::write('Tailscale.defaultAccessLevel', 'user');
/**
* Set timezone to Australian Eastern Time
* This handles both AEST and AEDT (daylight saving) automatically
*/
date_default_timezone_set('Australia/Sydney');

View file

@ -7,9 +7,9 @@
class DATABASE_CONFIG {
var $default = array(
'driver' => 'mysqli',
'driver' => 'mysql',
'persistent' => false,
'host' => 'cmc-prod-db',
'host' => '172.17.0.1',
'login' => 'cmc',
'password' => 'xVRQI&cA?7AU=hqJ!%au',
'database' => 'cmc',

View file

@ -0,0 +1,14 @@
<?php
class DATABASE_CONFIG {
var $default = array(
'driver' => 'mysql',
'persistent' => false,
'host' => '172.17.0.1',
'login' => 'staging',
'password' => 'stagingmoopwoopVerySecure',
'database' => 'staging',
'prefix' => '',
);
}

View file

@ -0,0 +1,143 @@
<?php
/* App Controller */
class AppController extends Controller {
var $components = array('RequestHandler');
var $uses = array('User');
var $helpers = array('Javascript', 'Time', 'Html', 'Form');
function beforeFilter() {
// Find the user that matches the HTTP basic auth user
$user = $this->User->find('first', array('recursive' => 0, 'conditions' => array('User.username'=>$_SERVER["PHP_AUTH_USER"])));
$this->set("currentuser", $user);
if($this->RequestHandler->isAjax()) {
Configure::write('debug', 0);
}
}
/**
* Check if the current logged in user is an admin
* @return boolean
*/
function isAdmin() {
$currentuser = $this->getCurrentUser();
if($currentuser['access_level'] == 'admin') {
return true;
}
else {
return false;
}
}
function isManager() {
$currentuser = $this->getCurrentUser();
if($currentuser['access_level'] == 'manager') {
return true;
}
else {
return false;
}
}
/**
* Read the current logged in user.
* @return array - the currently logged in user.
*/
function getCurrentUser() {
$user = $this->User->find('first', array('recursive' => 0, 'conditions' => array('User.username'=>$_SERVER["PHP_AUTH_USER"])));
return $user;
}
/**
* Return the id of the current user. False if not logged in.
*/
function getCurrentUserID() {
$currentuser = $this->getCurrentUser();
if($currentuser) {
return $currentuser['User']['id'];
}
else {
return false;
}
}
function calculateTotals($document, $gst) {
$totals = array('subtotal'=>0, 'gst'=>0, 'total'=>0);
foreach($document['LineItem'] as $lineitem) {
if($lineitem['option'] == 1) {
$totals['subtotal'] = 'TBA';
$totals['total'] = 'TBA';
$totals['gst'] = 'TBA';
return $totals;
}
else {
$totals['subtotal'] += $lineitem['net_price'];
}
}
if($gst == 1) {
$totals['gst'] = 0.1*$totals['subtotal'];
}
$totals['total'] = $totals['gst'] + $totals['subtotal'];
return $totals;
}
function unset_keys($array, $keys) {
foreach($keys as $key ) {
$array[$key] = null;
}
return $array;
}
function unset_multiple_keys($array, $keys) {
foreach($array as $index => $item) {
$array[$index]['id'] = null;
$array[$index]['document_id'] = null;
$array[$index]['costing_id'] = null;
}
}
/**
*
* @param <type> $year
* @param <type> $prevYear
* @return <type>
*/
function getFirstDayFY($year,$prevYear = false) {
if($prevYear == false) {
return mktime(0,0,0,7,1,$year);
}
else {
return mktime(0,0,0,7,1,$year-1);
}
}
/**
*
* @param <type> $year
* @return <int>
*/
function getLastDayFY($year) {
return mktime(23,59,59,6,30,$year);
}
}
?>

View file

@ -39,83 +39,20 @@ class AttachmentsController extends AppController {
function add() {
if (!empty($this->data)) {
// Check if file was uploaded
if (empty($this->data['Attachment']['file']['tmp_name'])) {
$error = 'No file uploaded';
if (isset($this->data['Attachment']['file']['error'])) {
$errorCodes = array(
UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize',
UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE',
UPLOAD_ERR_PARTIAL => 'File only partially uploaded',
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
UPLOAD_ERR_EXTENSION => 'File upload stopped by extension',
);
$errorCode = $this->data['Attachment']['file']['error'];
$error = isset($errorCodes[$errorCode]) ? $errorCodes[$errorCode] : 'Unknown error: ' . $errorCode;
}
$this->Session->setFlash(__('File upload error: ' . $error, true));
$principles = $this->Attachment->Principle->find('list');
$this->set(compact('products', 'principles'));
return;
}
// Proxy the upload request to the Go application
$goHost = getenv('GO_APP_HOST');
$goUrl = 'http://' . $goHost . '/go/attachments/upload';
// Prepare the multipart form data for the Go app
$postFields = array();
$postFields['file'] = new CURLFile(
$this->data['Attachment']['file']['tmp_name'],
$this->data['Attachment']['file']['type'],
$this->data['Attachment']['file']['name']
);
if (!empty($this->data['Attachment']['name'])) {
$postFields['name'] = $this->data['Attachment']['name'];
$attachment = $this->Attachment->process_attachment($this->data);
if(!$attachment) {
$this->Session->setFlash('The Attachment could not be saved. The filename exists');
}
if (!empty($this->data['Attachment']['description'])) {
$postFields['description'] = $this->data['Attachment']['description'];
}
if (!empty($this->data['Attachment']['principle_id'])) {
$postFields['principle_id'] = $this->data['Attachment']['principle_id'];
}
// Make the request to Go app
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $goUrl);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Accept: application/json'
));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($httpCode == 201) {
$this->Session->setFlash(__('The Attachment has been saved', true));
$this->redirect(array('action'=>'index'));
} else {
$errorMsg = 'The Attachment could not be saved.';
if ($curlError) {
$errorMsg .= ' cURL Error: ' . $curlError;
} elseif ($response) {
$errorMsg .= ' Response: ' . $response;
else {
$this->Attachment->create();
if ($this->Attachment->save($attachment)) {
$this->Session->setFlash(__('The Attachment has been saved', true));
$this->redirect(array('action'=>'index'));
} else {
$errorMsg .= ' HTTP Code: ' . $httpCode;
$this->Session->setFlash(__('The Attachment could not be saved. Please, try again.', true));
}
error_log('Attachment upload failed: ' . $errorMsg);
$this->Session->setFlash(__($errorMsg, true));
}
}
$principles = $this->Attachment->Principle->find('list');

View file

@ -540,10 +540,6 @@ ENDINSTRUCTIONS;
$number_of_revisions = $this->Document->Quote->findCount('Quote.enquiry_id ='. $enquiryid);
$newDoc['Document']['revision'] = $number_of_revisions;
$newDoc['Document']['type'] = 'quote';
// user_id for the new revision will be the current user
$currentUser = $this->GetCurrentUser();
$newDoc['Document']['user_id'] = $currentUser['User']['id'];
$newDoc['DocPage'] = $document['DocPage'];
@ -600,6 +596,7 @@ ENDINSTRUCTIONS;
// Store job IDs to be processed after the main save
$newDoc['_job_ids'] = $job_ids;
}
print_r($newDoc);
}
@ -1208,13 +1205,13 @@ EOT;
}
function format_email($email) {
$email = trim($email);
// Basic RFC 5322 email validation
if (!preg_match('/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/', $email)) {
return '';
}
return $email;
function format_email($email) {
$email = trim($email);
// Basic RFC 5322 email validation
if (!preg_match('/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/', $email)) {
return '';
}
return "$email <$email>";
}
function parse_email_to_array($input) {
@ -1328,23 +1325,14 @@ function email_pdf_with_custom_recipients($id = null, $to = null, $cc = null, $b
$msg = 'Invalid recipient email address.';
echo json_encode(array('success' => false, 'message' => $msg));
return;
} else {
// Pass as array - EmailComponent now properly handles arrays for TO field
$this->Email->to = $toArray;
}
} else {
$this->Email->to = implode(', ', $toArray);
}
$ccArray = $this->parse_email_to_array($cc);
if (!empty($ccArray)) {
$this->Email->cc = $ccArray;
}
$bccArray = $this->parse_email_to_array($bcc);
// Add always BCC recipients
// These emails will always be included in the BCC list, regardless of user input
$alwaysBcc = array('<carpis@cmctechnologies.com.au>', '<mcarpis@cmctechnologies.com.au>');
foreach ($alwaysBcc as $bccEmail) {
if (!in_array($bccEmail, $bccArray)) {
$bccArray[] = $bccEmail;
}
}
if (!empty($bccArray)) {
$this->Email->bcc = $bccArray;
}

View file

@ -12,20 +12,11 @@ class EmailAttachmentsController extends AppController {
// GET vault.cmctechnologies.com.au/filename
// HTTP basic auth, or some sort of preshared key
// service hits S3 if required. Cached on disk for $sometime
$file = $this->EmailAttachment->findById($id);
// Try legacy emails directory first (where vault saves files)
$file_path = '/var/www/emails';
$full_path = $file_path."/".$file['EmailAttachment']['name'];
// Fallback to attachments directory if not found in emails
if(!file_exists($full_path)) {
$file_path = Configure::read('attachments_directory');
$full_path = $file_path."/".$file['EmailAttachment']['name'];
}
if(file_exists($full_path)) {
$file_path = Configure::read('email_directory');
if(file_exists($file_path."/".$file['EmailAttachment']['name'])) {
@ -42,13 +33,12 @@ class EmailAttachmentsController extends AppController {
header('Content-length: ' . $file['EmailAttachment']['size']);
header('Content-Disposition: attachment; filename='.$filename);
readfile($full_path);
readfile($file_path."/".$file['EmailAttachment']['name']);
exit();
}
else {
echo "ERROR: File Not Found";
echo '\n';
echo "ERROR!! : File Not Found";
echo $file['EmailAttachment']['filename'];
die();
}
@ -65,17 +55,9 @@ class EmailAttachmentsController extends AppController {
$file = $this->EmailAttachment->find('first', array('conditions'=>array('EmailAttachment.id'=>$id)));
//$this->set('attachment', $file);
// Try legacy emails directory first (where vault saves files)
$file_path = '/var/www/emails';
$full_path = $file_path."/".$file['EmailAttachment']['name'];
$file_path = Configure::read('email_directory');
// Fallback to attachments directory if not found in emails
if(!file_exists($full_path)) {
$file_path = Configure::read('attachments_directory');
$full_path = $file_path."/".$file['EmailAttachment']['name'];
}
$contents = file_get_contents($full_path);
$contents = file_get_contents($file_path."/".$file['EmailAttachment']['name']);
if($file['EmailAttachment']['type'] == 'text/plain') {

Some files were not shown because too many files have changed in this diff Show more