Add complete frontend implementation and Docker deployment setup
- Implement React frontend with TypeScript and Tailwind CSS - Add scrappbook.de-inspired UI design with photo galleries - Implement authentication, photo viewing, and download features - Add Docker Swarm configuration with Traefik reverse proxy - Set up Drone CI/CD pipeline for automated deployments - Add monitoring stack with Prometheus and Grafana - Create comprehensive deployment documentation - Add simple local development setup with docker-compose.local.yml Features: - Password-protected galleries with expiration warnings - Responsive photo grid with lightbox viewer - Bulk download functionality - Hot reload development environment - Email testing with Mailhog - Production-ready deployment scripts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
const bcrypt = require('bcrypt');
|
||||
const { db, initializeDatabase } = require('../src/database/db');
|
||||
|
||||
async function runMigrations() {
|
||||
console.log('Running database migrations...');
|
||||
|
||||
try {
|
||||
// Initialize tables
|
||||
await initializeDatabase();
|
||||
|
||||
// Create default admin user if none exists
|
||||
const adminExists = await db('admin_users').first();
|
||||
if (!adminExists) {
|
||||
const defaultPassword = 'admin123'; // Change this!
|
||||
const passwordHash = await bcrypt.hash(defaultPassword, 10);
|
||||
|
||||
await db('admin_users').insert({
|
||||
username: 'admin',
|
||||
email: 'admin@example.com',
|
||||
password_hash: passwordHash
|
||||
});
|
||||
|
||||
console.log('Default admin user created:');
|
||||
console.log('Username: admin');
|
||||
console.log('Password: admin123');
|
||||
console.log('⚠️ Please change this password immediately!');
|
||||
}
|
||||
|
||||
console.log('Migrations completed successfully');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
runMigrations();
|
||||
@@ -0,0 +1,28 @@
|
||||
const sharp = require('sharp');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
const THUMBNAIL_WIDTH = 300;
|
||||
const THUMBNAIL_PATH = path.join(__dirname, '../../../storage/thumbnails');
|
||||
|
||||
async function generateThumbnail(imagePath) {
|
||||
const filename = path.basename(imagePath);
|
||||
const thumbnailFilename = `thumb_${filename}`;
|
||||
const thumbnailPath = path.join(THUMBNAIL_PATH, thumbnailFilename);
|
||||
|
||||
// Ensure thumbnail directory exists
|
||||
await fs.mkdir(THUMBNAIL_PATH, { recursive: true });
|
||||
|
||||
// Generate thumbnail
|
||||
await sharp(imagePath)
|
||||
.resize(THUMBNAIL_WIDTH, null, {
|
||||
withoutEnlargement: true,
|
||||
fit: 'inside'
|
||||
})
|
||||
.jpeg({ quality: 80 })
|
||||
.toFile(thumbnailPath);
|
||||
|
||||
return path.relative(path.join(__dirname, '../../../storage'), thumbnailPath);
|
||||
}
|
||||
|
||||
module.exports = { generateThumbnail };
|
||||
@@ -0,0 +1,31 @@
|
||||
const winston = require('winston');
|
||||
const path = require('path');
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: path.join(__dirname, '../../../logs/error.log'),
|
||||
level: 'error'
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: path.join(__dirname, '../../../logs/combined.log')
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.add(new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
module.exports = logger;
|
||||
Reference in New Issue
Block a user