Compare commits
No commits in common. "master" and "go" have entirely different histories.
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -2,8 +2,6 @@ app/tmp/*
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
.env.prod
|
|
||||||
.env.stg
|
|
||||||
app/vendors/tcpdf/cache/*
|
app/vendors/tcpdf/cache/*
|
||||||
app/tests/*
|
app/tests/*
|
||||||
app/emails/*
|
app/emails/*
|
||||||
|
|
@ -13,11 +11,3 @@ app/vaultmsgs/*
|
||||||
app/cake_eclipse_helper.php
|
app/cake_eclipse_helper.php
|
||||||
app/webroot/pdf/*
|
app/webroot/pdf/*
|
||||||
app/webroot/attachments_files/*
|
app/webroot/attachments_files/*
|
||||||
backups/*
|
|
||||||
|
|
||||||
# Go binaries
|
|
||||||
go/server
|
|
||||||
go/vault
|
|
||||||
go/go.mod
|
|
||||||
go/go.sum
|
|
||||||
go/goose.env
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
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
|
- **Legacy CakePHP 1.2.5 application** (2008-era) - Primary business logic
|
||||||
- **Modern Go API** (in `/go/`) - New development using sqlc and Gorilla Mux
|
- **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.
|
**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
|
### Go Application Development
|
||||||
```bash
|
```bash
|
||||||
# Navigate to Go app directory
|
# Navigate to Go app directory
|
||||||
cd go
|
cd go-app
|
||||||
|
|
||||||
# Configure private module access (first time only)
|
# Configure private module access (first time only)
|
||||||
go env -w GOPRIVATE=code.springupsoftware.com
|
go env -w GOPRIVATE=code.springupsoftware.com
|
||||||
|
|
@ -80,7 +80,7 @@ make build
|
||||||
### Go Application (Modern)
|
### Go Application (Modern)
|
||||||
- **Framework**: Gorilla Mux (HTTP router)
|
- **Framework**: Gorilla Mux (HTTP router)
|
||||||
- **Database**: sqlc for type-safe SQL queries
|
- **Database**: sqlc for type-safe SQL queries
|
||||||
- **Location**: `/go/`
|
- **Location**: `/go-app/`
|
||||||
- **Structure**:
|
- **Structure**:
|
||||||
- `cmd/server/` - Main application entry point
|
- `cmd/server/` - Main application entry point
|
||||||
- `internal/cmc/handlers/` - HTTP request handlers
|
- `internal/cmc/handlers/` - HTTP request handlers
|
||||||
|
|
|
||||||
|
|
@ -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
|
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.
|
# By default, simply start apache.
|
||||||
CMD /usr/sbin/apache2ctl -D FOREGROUND
|
CMD /usr/sbin/apache2ctl -D FOREGROUND
|
||||||
|
|
@ -22,14 +22,14 @@ RUN go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
|
||||||
# Generate sqlc code
|
# Generate sqlc code
|
||||||
RUN sqlc generate
|
RUN sqlc generate
|
||||||
|
|
||||||
# Build the application with staging tags
|
# Build the application
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -tags staging -o server cmd/server/main.go
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server cmd/server/main.go
|
||||||
|
|
||||||
# Runtime stage
|
# Runtime stage
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
# Install runtime dependencies and debugging tools for staging
|
# Install runtime dependencies
|
||||||
RUN apk --no-cache add ca-certificates curl net-tools
|
RUN apk --no-cache add ca-certificates
|
||||||
|
|
||||||
WORKDIR /root/
|
WORKDIR /root/
|
||||||
|
|
||||||
|
|
@ -40,18 +40,11 @@ COPY --from=builder /app/server .
|
||||||
COPY go-app/templates ./templates
|
COPY go-app/templates ./templates
|
||||||
COPY go-app/static ./static
|
COPY go-app/static ./static
|
||||||
|
|
||||||
# Copy staging environment file
|
# Copy .env file if needed
|
||||||
COPY go-app/.env.staging .env
|
COPY go-app/.env.example .env
|
||||||
|
|
||||||
# Create credentials directory
|
|
||||||
RUN mkdir -p ./credentials
|
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 8080
|
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
|
# Run the application
|
||||||
CMD ["./server"]
|
CMD ["./server"]
|
||||||
|
|
@ -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"]
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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/
|
|
||||||
|
|
@ -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"]
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
FROM mariadb:latest
|
|
||||||
|
|
@ -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"]
|
|
||||||
|
|
@ -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
63
Dockerfile_stg
Normal 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
18
MIGRATION.md
Normal 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
138
README.md
|
|
@ -1,23 +1,9 @@
|
||||||
# cmc-sales
|
# 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
|
## Development Setup
|
||||||
|
|
||||||
|
CMC Sales now runs both legacy CakePHP and modern Go applications side by side.
|
||||||
|
|
||||||
### Quick Start
|
### Quick Start
|
||||||
|
|
||||||
``` shell
|
``` shell
|
||||||
|
|
@ -59,53 +45,105 @@ Both applications share the same database, allowing for gradual migration.
|
||||||
- **Go Application**: Requires Go 1.23+ (for latest sqlc)
|
- **Go Application**: Requires Go 1.23+ (for latest sqlc)
|
||||||
- Alternative: Use `Dockerfile.go.legacy` with Go 1.21 and sqlc v1.26.0
|
- 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
|
cd cmc-playbooks
|
||||||
HostName node0.prd.springupsoftware.com
|
# Add the hostname of your new server to the inventory.txt
|
||||||
User cmc
|
ansible-playbook -i inventory.txt setup.yml
|
||||||
IdentityFile ~/.ssh/cmc
|
```
|
||||||
|
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
|
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
|
||||||
Deploy to staging or production using the scripts in the `deploy/` directory:
|
```
|
||||||
|
cmc@cmc:~$ cat .ssh/id_rsa.pub
|
||||||
**Deploy to Staging:**
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFIdoWVp2pGDb46ubW6jkfIpREMa/veD6xZVAtnj3WG1sX7NEUlQYq3RKbZ5CThlw6GKMSYoIsIqk7p6zSoJHGlJSLxoJ0edKflciMUFMTQrdm4T1USXsK+gd0C4DUCyVkYFOs37sy+JtziymnBTm7iOeVI3aMxwfoCOs6mNiD0ettjJT6WtVyy0ZTb6yU4uz7CHj1IGsvwsoKJWPGwJrZ/MfByNl6aJ8R/8zDwbtP06owKD4b3ZPgakM3nYRRoKzHZ/SClz50SXMKC4/nmFY9wLuuMhCWK+9x4/4VPSnxXESOlENMfUoa1IY4osAnZCtaFrWDyenJ+spZrNfgcscD ansible-generated on cmc
|
||||||
```bash
|
|
||||||
./scripts/deploy/deploy-stg.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Deploy to Production:**
|
6. Clone the cmc-sales repo
|
||||||
```bash
|
```
|
||||||
./deploy/deploy-prod.sh
|
git clone git@gitlab.com:minimalist.software/cmc-sales.git
|
||||||
```
|
```
|
||||||
|
|
||||||
**Rebuild without cache (useful after dependency changes):**
|
7. As root on new server configure mySQL user cmc
|
||||||
```bash
|
Note: get password from app/config/database.php
|
||||||
./scripts/deploy/deploy-prod.sh --no-cache
|
(or set a new one and change it)
|
||||||
./scripts/deploy/deploy-stg.sh --no-cache
|
```
|
||||||
|
# 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
|
In the shared google drive:
|
||||||
2. Clones or updates the appropriate git branch (`stg` or `prod`)
|
eg. backups/database/backup_20191217_21001.sql.gz
|
||||||
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
|
|
||||||
|
|
||||||
### 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:
|
9. Restore backup to cmc database
|
||||||
- A single Caddy reverse proxy (handles HTTPS and basic authentication for both environments)
|
```
|
||||||
- Separate Docker containers for each environment's PHP and Go applications
|
zcat backup_* | mysql -u cmc -p
|
||||||
- Separate MariaDB database instances
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,4 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
//EOF
|
//EOF
|
||||||
|
|
||||||
require_once(dirname(__FILE__) . '/php7_compat.php');
|
|
||||||
?>
|
?>
|
||||||
|
|
@ -47,13 +47,19 @@ Configure::write('version', '1.0.1');
|
||||||
|
|
||||||
$host = $_SERVER['HTTP_HOST'];
|
$host = $_SERVER['HTTP_HOST'];
|
||||||
|
|
||||||
|
/*Configure::write('smtp_settings', array(
|
||||||
// SMTP settings
|
'port' => '587',
|
||||||
Configure::write('smtp_settings', array(
|
'timeout' => '60',
|
||||||
'port' => '25',
|
'host' => 'smtp-relay.gmail.com',
|
||||||
'timeout' => '30',
|
'username' => 'sales',
|
||||||
'host' => 'postfix'
|
'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
|
//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/';
|
$basedir = '/var/www/cmc-sales/app/';
|
||||||
Cache::config('default', array(
|
Cache::config('default', array(
|
||||||
'engine' => 'File', //[required]
|
'engine' => 'File', //[required]
|
||||||
'duration'=> 3600, //[optional]
|
'duration'=> 3600, //[optional]
|
||||||
'probability'=> 100, //[optional]
|
'probability'=> 100, //[optional]
|
||||||
'path' => '/home/cmc/cmc-sales/app/tmp/', //[optional] use system tmp directory - remember to use absolute path
|
'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
|
'prefix' => 'cake_', //[optional] prefix every cache file with this string
|
||||||
'lock' => false, //[optional] use file locking
|
'lock' => false, //[optional] use file locking
|
||||||
'serialize' => true,
|
'serialize' => true,
|
||||||
));
|
));
|
||||||
|
|
||||||
Configure::write('email_directory', '/var/www/emails');
|
Configure::write('email_directory', '/var/www/emails');
|
||||||
Configure::write('pdf_directory', $basedir.'webroot/pdf/');
|
Configure::write('pdf_directory', $basedir.'/webroot/pdf/');
|
||||||
Configure::write('attachments_directory', $basedir.'webroot/attachments_files/');
|
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.classname', 'DbAcl');
|
||||||
Configure::write('Acl.database', 'default');
|
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');
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
class DATABASE_CONFIG {
|
class DATABASE_CONFIG {
|
||||||
|
|
||||||
var $default = array(
|
var $default = array(
|
||||||
'driver' => 'mysqli',
|
'driver' => 'mysql',
|
||||||
'persistent' => false,
|
'persistent' => false,
|
||||||
'host' => 'cmc-prod-db',
|
'host' => '172.17.0.1',
|
||||||
'login' => 'cmc',
|
'login' => 'cmc',
|
||||||
'password' => 'xVRQI&cA?7AU=hqJ!%au',
|
'password' => 'xVRQI&cA?7AU=hqJ!%au',
|
||||||
'database' => 'cmc',
|
'database' => 'cmc',
|
||||||
14
app/config/database_stg.php
Normal file
14
app/config/database_stg.php
Normal 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' => '',
|
||||||
|
);
|
||||||
|
}
|
||||||
143
app/controllers/app_controller.php
Executable file
143
app/controllers/app_controller.php
Executable 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
@ -39,83 +39,20 @@ class AttachmentsController extends AppController {
|
||||||
|
|
||||||
function add() {
|
function add() {
|
||||||
if (!empty($this->data)) {
|
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
|
$attachment = $this->Attachment->process_attachment($this->data);
|
||||||
$goHost = getenv('GO_APP_HOST');
|
if(!$attachment) {
|
||||||
$goUrl = 'http://' . $goHost . '/go/attachments/upload';
|
$this->Session->setFlash('The Attachment could not be saved. The filename exists');
|
||||||
|
|
||||||
// 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'];
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if (!empty($this->data['Attachment']['description'])) {
|
$this->Attachment->create();
|
||||||
$postFields['description'] = $this->data['Attachment']['description'];
|
|
||||||
}
|
if ($this->Attachment->save($attachment)) {
|
||||||
|
$this->Session->setFlash(__('The Attachment has been saved', true));
|
||||||
if (!empty($this->data['Attachment']['principle_id'])) {
|
$this->redirect(array('action'=>'index'));
|
||||||
$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 {
|
} 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');
|
$principles = $this->Attachment->Principle->find('list');
|
||||||
|
|
@ -540,10 +540,6 @@ ENDINSTRUCTIONS;
|
||||||
$number_of_revisions = $this->Document->Quote->findCount('Quote.enquiry_id ='. $enquiryid);
|
$number_of_revisions = $this->Document->Quote->findCount('Quote.enquiry_id ='. $enquiryid);
|
||||||
$newDoc['Document']['revision'] = $number_of_revisions;
|
$newDoc['Document']['revision'] = $number_of_revisions;
|
||||||
$newDoc['Document']['type'] = 'quote';
|
$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'];
|
$newDoc['DocPage'] = $document['DocPage'];
|
||||||
|
|
@ -600,6 +596,7 @@ ENDINSTRUCTIONS;
|
||||||
// Store job IDs to be processed after the main save
|
// Store job IDs to be processed after the main save
|
||||||
$newDoc['_job_ids'] = $job_ids;
|
$newDoc['_job_ids'] = $job_ids;
|
||||||
}
|
}
|
||||||
|
print_r($newDoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1208,13 +1205,13 @@ EOT;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function format_email($email) {
|
function format_email($email) {
|
||||||
$email = trim($email);
|
$email = trim($email);
|
||||||
// Basic RFC 5322 email validation
|
// Basic RFC 5322 email validation
|
||||||
if (!preg_match('/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/', $email)) {
|
if (!preg_match('/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/', $email)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return $email;
|
return "$email <$email>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse_email_to_array($input) {
|
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.';
|
$msg = 'Invalid recipient email address.';
|
||||||
echo json_encode(array('success' => false, 'message' => $msg));
|
echo json_encode(array('success' => false, 'message' => $msg));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// Pass as array - EmailComponent now properly handles arrays for TO field
|
$this->Email->to = implode(', ', $toArray);
|
||||||
$this->Email->to = $toArray;
|
}
|
||||||
}
|
|
||||||
$ccArray = $this->parse_email_to_array($cc);
|
$ccArray = $this->parse_email_to_array($cc);
|
||||||
if (!empty($ccArray)) {
|
if (!empty($ccArray)) {
|
||||||
$this->Email->cc = $ccArray;
|
$this->Email->cc = $ccArray;
|
||||||
}
|
}
|
||||||
$bccArray = $this->parse_email_to_array($bcc);
|
$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)) {
|
if (!empty($bccArray)) {
|
||||||
$this->Email->bcc = $bccArray;
|
$this->Email->bcc = $bccArray;
|
||||||
}
|
}
|
||||||
|
|
@ -12,20 +12,11 @@ class EmailAttachmentsController extends AppController {
|
||||||
// GET vault.cmctechnologies.com.au/filename
|
// GET vault.cmctechnologies.com.au/filename
|
||||||
// HTTP basic auth, or some sort of preshared key
|
// HTTP basic auth, or some sort of preshared key
|
||||||
// service hits S3 if required. Cached on disk for $sometime
|
// service hits S3 if required. Cached on disk for $sometime
|
||||||
|
|
||||||
$file = $this->EmailAttachment->findById($id);
|
$file = $this->EmailAttachment->findById($id);
|
||||||
|
|
||||||
// Try legacy emails directory first (where vault saves files)
|
$file_path = Configure::read('email_directory');
|
||||||
$file_path = '/var/www/emails';
|
if(file_exists($file_path."/".$file['EmailAttachment']['name'])) {
|
||||||
$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)) {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -42,13 +33,12 @@ class EmailAttachmentsController extends AppController {
|
||||||
header('Content-length: ' . $file['EmailAttachment']['size']);
|
header('Content-length: ' . $file['EmailAttachment']['size']);
|
||||||
header('Content-Disposition: attachment; filename='.$filename);
|
header('Content-Disposition: attachment; filename='.$filename);
|
||||||
|
|
||||||
readfile($full_path);
|
readfile($file_path."/".$file['EmailAttachment']['name']);
|
||||||
|
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
echo "ERROR: File Not Found";
|
echo "ERROR!! : File Not Found";
|
||||||
echo '\n';
|
|
||||||
echo $file['EmailAttachment']['filename'];
|
echo $file['EmailAttachment']['filename'];
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
@ -65,17 +55,9 @@ class EmailAttachmentsController extends AppController {
|
||||||
$file = $this->EmailAttachment->find('first', array('conditions'=>array('EmailAttachment.id'=>$id)));
|
$file = $this->EmailAttachment->find('first', array('conditions'=>array('EmailAttachment.id'=>$id)));
|
||||||
//$this->set('attachment', $file);
|
//$this->set('attachment', $file);
|
||||||
|
|
||||||
// Try legacy emails directory first (where vault saves files)
|
$file_path = Configure::read('email_directory');
|
||||||
$file_path = '/var/www/emails';
|
|
||||||
$full_path = $file_path."/".$file['EmailAttachment']['name'];
|
|
||||||
|
|
||||||
// Fallback to attachments directory if not found in emails
|
$contents = file_get_contents($file_path."/".$file['EmailAttachment']['name']);
|
||||||
if(!file_exists($full_path)) {
|
|
||||||
$file_path = Configure::read('attachments_directory');
|
|
||||||
$full_path = $file_path."/".$file['EmailAttachment']['name'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$contents = file_get_contents($full_path);
|
|
||||||
|
|
||||||
|
|
||||||
if($file['EmailAttachment']['type'] == 'text/plain') {
|
if($file['EmailAttachment']['type'] == 'text/plain') {
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue