Compare commits

..

No commits in common. "641544f7176bf24ba0436e872c6f3a4cdb6ede84" and "63ab72161d6781883b3f1f087f09cbb970b106d1" have entirely different histories.

11 changed files with 423 additions and 195 deletions

View File

@ -49,9 +49,6 @@ coverage
ehthumbs.db ehthumbs.db
Thumbs.db Thumbs.db
# Explicitly include start.sh
!start.sh
# Git # Git
.git .git
.gitignore .gitignore

30
.env.dokploy Normal file
View File

@ -0,0 +1,30 @@
# Dokploy Environment Variables
# Use these values in your Dokploy environment variables section
NODE_ENV=production
APP_PORT=5173
# Database (uses Docker volume)
DATABASE_URL=file:/app/data/production.db
# Security - CHANGE THESE VALUES!
SESSION_SECRET=your-super-secure-session-secret-change-this-in-production-min-32-chars
ENCRYPTION_KEY=production-secure-encryption-key!
SUPER_ADMIN=superadmin
SUPER_ADMIN_EMAIL=admin@yourcompany.com
SUPER_ADMIN_PASSWORD=YourSecurePassword123!
# Domain (set to your actual domain)
DOMAIN=your-domain.com
# Mail Settings (optional - for password reset features)
MAIL_HOST=
MAIL_PORT=587
MAIL_SECURE=false
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM_NAME=Phosphat Report System
MAIL_FROM_EMAIL=
# Logging
LOG_LEVEL=info

39
.env.production Normal file
View File

@ -0,0 +1,39 @@
# Production Environment Variables
# Copy this file and rename to .env for production deployment
# Make sure to change all default values for security
# Application Settings
NODE_ENV=production
APP_PORT=5173
DOMAIN=your-domain.com
# Database
DATABASE_URL="file:/app/data/production.db"
# Security
SESSION_SECRET="your-super-secure-session-secret-change-this-in-production-min-32-chars"
ENCRYPTION_KEY="production-secure-encryption-key!"
# Super Admin Account (created on first run)
SUPER_ADMIN="superadmin"
SUPER_ADMIN_EMAIL="admin@yourcompany.com"
SUPER_ADMIN_PASSWORD="YourSecurePassword123!"
# Storage Paths (for bind mounts)
DATA_PATH=./data
BACKUP_PATH=./backups
# Backup Schedule (cron format)
BACKUP_SCHEDULE="0 2 * * *"
# Mail Settings (optional - for password reset features)
MAIL_HOST=""
MAIL_PORT="587"
MAIL_SECURE="false"
MAIL_USERNAME=""
MAIL_PASSWORD=""
MAIL_FROM_NAME="Phosphat Report System"
MAIL_FROM_EMAIL=""
# Logging (optional)
LOG_LEVEL="info"

View File

@ -1,147 +0,0 @@
# Deployment Guide for Phosphat Report App
This guide will help you deploy the Phosphat Report application on your VPS using the provided `compose.yml` file.
## Prerequisites
- Docker and Docker Compose installed on your VPS
- Git (to clone the repository)
- At least 1GB RAM and 10GB disk space
## Quick Deployment
1. **Clone the repository** to your VPS:
```bash
git clone <your-repo-url>
cd phosphat-report-app
```
2. **Deploy the application**:
```bash
docker-compose -f compose.yml up -d --build
```
3. **Check the status**:
```bash
docker-compose -f compose.yml ps
```
4. **Access your application**:
- URL: `http://your-vps-ip:3000`
- Default login: `superadmin` / `P@ssw0rd123!`
## Environment Variables (Hardcoded in compose.yml)
The following environment variables are already configured in the `compose.yml` file:
- **NODE_ENV**: `production`
- **DATABASE_URL**: `file:/app/data/production.db`
- **SESSION_SECRET**: `your-super-secure-session-secret-change-this-min-32-chars`
- **SUPER_ADMIN**: `superadmin`
- **SUPER_ADMIN_EMAIL**: `admin@yourcompany.com`
- **SUPER_ADMIN_PASSWORD**: `P@ssw0rd123!`
- **MAIL_HOST**: `smtp.gmail.com`
- **MAIL_PORT**: `587`
- **MAIL_USERNAME**: `your-email@gmail.com`
- **MAIL_PASSWORD**: `your-app-password`
## Services Included
### Main Application (`app`)
- **Port**: 3000
- **Database**: SQLite with persistent storage
- **Health Check**: Available at `/health` endpoint
- **Resource Limits**: 512MB RAM, 0.5 CPU
### Backup Service (`backup`)
- **Purpose**: Automatic daily database backups at 2 AM
- **Retention**: Keeps backups for 7 days
- **Location**: `/backup` volume
## Useful Commands
### View logs:
```bash
docker-compose -f compose.yml logs -f app
```
### Stop services:
```bash
docker-compose -f compose.yml down
```
### Restart services:
```bash
docker-compose -f compose.yml restart
```
### Manual backup:
```bash
docker-compose -f compose.yml exec app cp /app/data/production.db /app/data/backup_$(date +%Y%m%d_%H%M%S).db
```
### Check health:
```bash
curl http://localhost:3000/health
```
## Volumes
- **app_data**: Stores the SQLite database
- **app_logs**: Application logs
- **backup_data**: Database backups
## Security Notes
1. **Change default passwords** after first login
2. **Update email settings** in the application
3. **Configure firewall** to only allow necessary ports
4. **Use HTTPS** with a reverse proxy (nginx/traefik) for production
## Troubleshooting
### Application won't start:
```bash
# Check logs
docker-compose -f compose.yml logs app
# Rebuild without cache
docker-compose -f compose.yml build --no-cache app
```
### Database issues:
```bash
# Reset database (WARNING: This will delete all data)
docker-compose -f compose.yml down
docker volume rm $(docker volume ls -q | grep app_data)
docker-compose -f compose.yml up -d
```
### Port conflicts:
If port 3000 is already in use, edit the `compose.yml` file and change:
```yaml
ports:
- "3001:3000" # Change 3000 to any available port
```
## Updating the Application
1. **Pull latest changes**:
```bash
git pull origin main
```
2. **Rebuild and restart**:
```bash
docker-compose -f compose.yml up -d --build
```
## Support
For issues and support:
1. Check the application logs
2. Verify all services are running
3. Test the health endpoint
4. Check database connectivity
The application should be accessible at `http://your-vps-ip:3000` after successful deployment.

View File

@ -54,6 +54,39 @@ COPY --from=deps --chown=remix:nodejs /app/node_modules ./node_modules
RUN mkdir -p /app/data /app/logs && \ RUN mkdir -p /app/data /app/logs && \
chown -R remix:nodejs /app/data /app/logs chown -R remix:nodejs /app/data /app/logs
# Create startup script
COPY --chown=remix:nodejs <<EOF /app/start.sh
#!/bin/sh
set -e
echo "Starting Phosphat Report Application..."
# Run database migrations
echo "Running database migrations..."
npx prisma db push --accept-data-loss
# Run seed using production script
echo "Seeding database..."
if [ -f "scripts/seed-production.js" ]; then
echo "Using production seed script..."
node scripts/seed-production.js
else
echo "Production seed script not found, trying alternative methods..."
if [ -f "prisma/seed.js" ]; then
echo "Using JavaScript seed file..."
node prisma/seed.js
else
echo "No seeding method available, skipping..."
fi
fi
echo "Database setup complete. Starting application on port 3000..."
export PORT=3000
exec npx remix-serve ./build/server/index.js
EOF
RUN chmod +x /app/start.sh
USER remix USER remix
EXPOSE 3000 EXPOSE 3000
@ -67,4 +100,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
# Use dumb-init to handle signals properly # Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"] ENTRYPOINT ["dumb-init", "--"]
CMD ["sh", "-c", "echo 'Starting Phosphat Report Application...' && npx prisma db push --accept-data-loss && echo 'Seeding database...' && (test -f scripts/seed-production.js && node scripts/seed-production.js || test -f prisma/seed.js && node prisma/seed.js || echo 'No seeding method available, skipping...') && echo 'Database setup complete. Starting application on port 3000...' && exec npx remix-serve ./build/server/index.js"] CMD ["/app/start.sh"]

View File

@ -3,26 +3,28 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
args:
- NODE_ENV=production
image: phosphat-report:latest
container_name: phosphat-report-app container_name: phosphat-report-app
restart: unless-stopped restart: unless-stopped
ports: ports:
- "3000:3000" - "${APP_PORT:-5173}:3000"
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- PORT=3000 - PORT=3000
- DATABASE_URL=file:/app/data/production.db - DATABASE_URL=file:/app/data/production.db
- SESSION_SECRET=your-super-secure-session-secret-change-this-min-32-chars - SESSION_SECRET=${SESSION_SECRET}
- SUPER_ADMIN=superadmin - SUPER_ADMIN=${SUPER_ADMIN}
- SUPER_ADMIN_EMAIL=admin@yourcompany.com - SUPER_ADMIN_EMAIL=${SUPER_ADMIN_EMAIL}
- SUPER_ADMIN_PASSWORD=P@ssw0rd123! - SUPER_ADMIN_PASSWORD=${SUPER_ADMIN_PASSWORD}
- MAIL_HOST=smtp.gmail.com - MAIL_HOST=${MAIL_HOST:-}
- MAIL_PORT=587 - MAIL_PORT=${MAIL_PORT:-587}
- MAIL_SECURE=false - MAIL_SECURE=${MAIL_SECURE:-false}
- MAIL_USERNAME=your-email@gmail.com - MAIL_USERNAME=${MAIL_USERNAME:-}
- MAIL_PASSWORD=your-app-password - MAIL_PASSWORD=${MAIL_PASSWORD:-}
- MAIL_FROM_NAME=Phosphat Report System - MAIL_FROM_NAME=${MAIL_FROM_NAME:-Phosphat Report System}
- MAIL_FROM_EMAIL=noreply@yourcompany.com - MAIL_FROM_EMAIL=${MAIL_FROM_EMAIL:-}
- ENCRYPTION_KEY=phosphat-report-default-key-32b
volumes: volumes:
- app_data:/app/data - app_data:/app/data
- app_logs:/app/logs - app_logs:/app/logs

76
docker-compose.yml Normal file
View File

@ -0,0 +1,76 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
- NODE_ENV=production
image: phosphat-report:latest
container_name: phosphat-report-app
restart: unless-stopped
ports:
- "${APP_PORT:-5173}:3000"
environment:
- NODE_ENV=production
- PORT=3000
- DATABASE_URL=file:/app/data/production.db
- SESSION_SECRET=${SESSION_SECRET}
- SUPER_ADMIN=${SUPER_ADMIN}
- SUPER_ADMIN_EMAIL=${SUPER_ADMIN_EMAIL}
- SUPER_ADMIN_PASSWORD=${SUPER_ADMIN_PASSWORD}
- MAIL_HOST=${MAIL_HOST:-}
- MAIL_PORT=${MAIL_PORT:-587}
- MAIL_SECURE=${MAIL_SECURE:-false}
- MAIL_USERNAME=${MAIL_USERNAME:-}
- MAIL_PASSWORD=${MAIL_PASSWORD:-}
- MAIL_FROM_NAME=${MAIL_FROM_NAME:-Phosphat Report System}
- MAIL_FROM_EMAIL=${MAIL_FROM_EMAIL:-}
volumes:
- app_data:/app/data
- app_logs:/app/logs
networks:
- app_network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health", "||", "exit", "1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
backup:
image: alpine:latest
container_name: phosphat-report-backup
restart: unless-stopped
volumes:
- app_data:/data:ro
- backup_data:/backup
command: >
sh -c "
apk add --no-cache dcron sqlite &&
echo '0 2 * * * cp /data/production.db /backup/production_$(date +%Y%m%d_%H%M%S).db && find /backup -name \"production_*.db\" -mtime +7 -delete' | crontab - &&
crond -f
"
networks:
- app_network
depends_on:
- app
volumes:
app_data:
driver: local
app_logs:
driver: local
backup_data:
driver: local
networks:
app_network:
driver: bridge

Binary file not shown.

View File

@ -6,6 +6,62 @@ const prisma = new PrismaClient();
async function main() { async function main() {
console.log('🌱 Seeding database...'); console.log('🌱 Seeding database...');
// Seed Areas
const areas = await Promise.all([
prisma.area.upsert({
where: { name: 'Petra' },
update: {},
create: { name: 'Petra' }
}),
prisma.area.upsert({
where: { name: 'Jarash' },
update: {},
create: { name: 'Jarash' }
}),
prisma.area.upsert({
where: { name: 'Rum' },
update: {},
create: { name: 'Rum' }
})
]);
// Seed DredgerLocations
const dredgerLocations = await Promise.all([
prisma.dredgerLocation.upsert({
where: { name: 'SP1-1' },
update: {},
create: { name: 'SP1-1', class: 'SP' }
}),
prisma.dredgerLocation.upsert({
where: { name: 'SP1-2' },
update: {},
create: { name: 'SP1-2', class: 'SP' }
}),
prisma.dredgerLocation.upsert({
where: { name: 'C01' },
update: {},
create: { name: 'C01', class: 'C' }
}),
prisma.dredgerLocation.upsert({
where: { name: 'D1' },
update: {},
create: { name: 'D1', class: 'D' }
})
]);
// Seed ReclamationLocations
const reclamationLocations = await Promise.all([
prisma.reclamationLocation.upsert({
where: { name: 'Eastern Shoreline' },
update: {},
create: { name: 'Eastern Shoreline' }
}),
prisma.reclamationLocation.upsert({
where: { name: 'Western Shoreline' },
update: {},
create: { name: 'Western Shoreline' }
})
]);
// Seed Super Admin Employee // Seed Super Admin Employee
const superAdminUsername = process.env.SUPER_ADMIN || 'superadmin'; const superAdminUsername = process.env.SUPER_ADMIN || 'superadmin';
@ -24,12 +80,61 @@ async function main() {
} }
}); });
// Seed Foreman
await prisma.foreman.upsert({
where: { id: 1 },
update: {},
create: {
name: 'John Smith'
}
});
// Seed Equipment
const equipment = await Promise.all([
prisma.equipment.upsert({
where: { id: 1 },
update: {},
create: { id: 1, category: 'Dozer', model: 'Dozer6', number: 1 }
}),
prisma.equipment.upsert({
where: { id: 2 },
update: {},
create: { id: 2, category: 'Dozer', model: 'Dozer6', number: 2 }
}),
prisma.equipment.upsert({
where: { id: 3 },
update: {},
create: { id: 3, category: 'Dozer', model: 'Dozer7', number: 1 }
}),
prisma.equipment.upsert({
where: { id: 4 },
update: {},
create: { id: 4, category: 'Dozer', model: 'Dozer8', number: 1 }
}),
prisma.equipment.upsert({
where: { id: 5 },
update: {},
create: { id: 5, category: 'Loader', model: 'Loader', number: 1 }
}),
prisma.equipment.upsert({
where: { id: 6 },
update: {},
create: { id: 6, category: 'Excavator', model: 'Exc.', number: 1 }
}),
prisma.equipment.upsert({
where: { id: 7 },
update: {},
create: { id: 7, category: 'Excavator', model: 'Exc.', number: 9 }
})
]);
console.log('✅ Database seeded successfully!'); console.log('✅ Database seeded successfully!');
console.log(`Created ${areas.length} areas`);
console.log(`Created ${dredgerLocations.length} dredger locations`);
console.log(`Created ${reclamationLocations.length} reclamation locations`);
console.log(`Created 1 employee`); console.log(`Created 1 employee`);
console.log(`Created 1 foreman`);
console.log(`Created ${equipment.length} equipment records`);
} }
main() main()

View File

@ -6,7 +6,75 @@ const prisma = new PrismaClient()
async function main() { async function main() {
console.log('🌱 Seeding database...') console.log('🌱 Seeding database...')
// Seed Areas
const areas = await Promise.all([
prisma.area.upsert({
where: { name: 'Petra' },
update: {},
create: { name: 'Petra' }
}),
prisma.area.upsert({
where: { name: 'Jarash' },
update: {},
create: { name: 'Jarash' }
}),
prisma.area.upsert({
where: { name: 'Rum' },
update: {},
create: { name: 'Rum' }
})
])
// Seed DredgerLocations
const dredgerLocations = await Promise.all([
prisma.dredgerLocation.upsert({
where: { name: 'SP1-1' },
update: {},
create: { name: 'SP1-1', class: 'SP' }
}),
prisma.dredgerLocation.upsert({
where: { name: 'SP1-2' },
update: {},
create: { name: 'SP1-2', class: 'SP' }
}),
prisma.dredgerLocation.upsert({
where: { name: 'C01' },
update: {},
create: { name: 'C01', class: 'C' }
}),
prisma.dredgerLocation.upsert({
where: { name: 'D1' },
update: {},
create: { name: 'D1', class: 'D' }
})
])
// Seed ReclamationLocations
const reclamationLocations = await Promise.all([
prisma.reclamationLocation.upsert({
where: { name: 'Eastern Shoreline' },
update: {},
create: { name: 'Eastern Shoreline' }
}),
prisma.reclamationLocation.upsert({
where: { name: 'Western Shoreline' },
update: {},
create: { name: 'Western Shoreline' }
})
])
// Seed Employee
// const employee = await prisma.employee.upsert({
// where: { username: 'superuser' },
// update: {},
// create: {
// name: 'Super Admin User',
// authLevel: 3,
// username: '',
// email: '@gmail.com',
// password: bcrypt.hashSync('', 10)
// }
// })
// Seed Employee // Seed Employee
//use the .env file SUPER_ADMIN, SUPER_ADMIN_EMAIL and SUPER_ADMIN_PASSWORD //use the .env file SUPER_ADMIN, SUPER_ADMIN_EMAIL and SUPER_ADMIN_PASSWORD
@ -24,9 +92,61 @@ async function main() {
}) })
// Seed Foreman
const foreman = await prisma.foreman.upsert({
where: { id: 1 },
update: {},
create: {
name: 'John Smith'
}
})
// Seed Equipment
const equipment = await Promise.all([
prisma.equipment.upsert({
where: { id: 1 },
update: {},
create: { id: 1, category: 'Dozer', model: 'Dozer6', number: 1 }
}),
prisma.equipment.upsert({
where: { id: 2 },
update: {},
create: { id: 2, category: 'Dozer', model: 'Dozer6', number: 2 }
}),
prisma.equipment.upsert({
where: { id: 3 },
update: {},
create: { id: 3, category: 'Dozer', model: 'Dozer7', number: 1 }
}),
prisma.equipment.upsert({
where: { id: 4 },
update: {},
create: { id: 4, category: 'Dozer', model: 'Dozer8', number: 1 }
}),
prisma.equipment.upsert({
where: { id: 5 },
update: {},
create: { id: 5, category: 'Loader', model: 'Loader', number: 1 }
}),
prisma.equipment.upsert({
where: { id: 6 },
update: {},
create: { id: 6, category: 'Excavator', model: 'Exc.', number: 1 }
}),
prisma.equipment.upsert({
where: { id: 7 },
update: {},
create: { id: 7, category: 'Excavator', model: 'Exc.', number: 9 }
})
])
console.log('✅ Database seeded successfully!') console.log('✅ Database seeded successfully!')
console.log(`Created ${areas.length} areas`)
console.log(`Created ${dredgerLocations.length} dredger locations`)
console.log(`Created ${reclamationLocations.length} reclamation locations`)
console.log(`Created 1 employee`)
console.log(`Created 1 foreman`)
console.log(`Created ${equipment.length} equipment records`)
} }
main() main()

View File

@ -1,27 +0,0 @@
#!/bin/sh
set -e
echo "Starting Phosphat Report Application..."
# Run database migrations
echo "Running database migrations..."
npx prisma db push --accept-data-loss
# Run seed using production script
echo "Seeding database..."
if [ -f "scripts/seed-production.js" ]; then
echo "Using production seed script..."
node scripts/seed-production.js
else
echo "Production seed script not found, trying alternative methods..."
if [ -f "prisma/seed.js" ]; then
echo "Using JavaScript seed file..."
node prisma/seed.js
else
echo "No seeding method available, skipping..."
fi
fi
echo "Database setup complete. Starting application on port 3000..."
export PORT=3000
exec npx remix-serve ./build/server/index.js