Add Docker support and fix rating change extraction

- Add Dockerfile with optimized Puppeteer configuration for containers
- Add .dockerignore for efficient builds
- Fix rating change regex to match actual PDGA format (no brackets)
- Update Puppeteer launch args for Docker compatibility
- Use new headless mode to resolve deprecation warning

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Samuel Enocsson
2025-08-12 11:20:51 +02:00
parent deb162dc13
commit 8e07dc6c73
3 changed files with 76 additions and 20 deletions
+7
View File
@@ -0,0 +1,7 @@
node_modules
.git
.gitignore
README.md
Dockerfile
.dockerignore
*.log
+42
View File
@@ -0,0 +1,42 @@
# Use official Node.js runtime as base image
FROM node:18-alpine
# Install Chromium and dependencies for Puppeteer
RUN apk add --no-cache \
chromium \
nss \
freetype \
freetype-dev \
harfbuzz \
ca-certificates \
ttf-freefont
# Tell Puppeteer to skip installing Chromium. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application code
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Change ownership of the app directory
RUN chown -R nextjs:nodejs /app
USER nextjs
# Expose port
EXPOSE 3000
# Start the application
CMD ["npm", "start"]
+27 -20
View File
@@ -20,7 +20,18 @@ async function scrapePDGARating(pdgaNumber) {
return cached.data;
}
const browser = await puppeteer.launch({ headless: true });
const browser = await puppeteer.launch({
headless: "new",
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-zygote',
'--disable-gpu'
]
});
const page = await browser.newPage();
try {
@@ -37,29 +48,14 @@ async function scrapePDGARating(pdgaNumber) {
for (const el of elements) {
const text = el.innerText || el.textContent;
if (text.includes('Current Rating:')) {
console.log('Found rating text:', text);
const ratingMatch = text.match(/Current Rating:\s*(\d+)/);
// Try different patterns for rating change
const changePatterns = [
/\[(\+\d+)\]/,
/\[(\-\d+)\]/,
/(\+\d+)/,
/(\-\d+)/
];
let change = null;
for (const pattern of changePatterns) {
const match = text.match(pattern);
if (match) {
change = match[1];
break;
}
}
// Look for rating change pattern: "Current Rating: 911 +6 (as of..."
const changeMatch = text.match(/Current Rating:\s*\d+\s+([+\-]\d+)\s+\(as of/);
return {
rating: ratingMatch ? ratingMatch[1] : null,
change: change
change: changeMatch ? changeMatch[1] : null
};
}
}
@@ -318,7 +314,18 @@ app.get('/api/ratings', async (req, res) => {
app.post('/api/predicted-rating/:pdgaNumber', async (req, res) => {
try {
const { pdgaNumber } = req.params;
const browser = await puppeteer.launch({ headless: true });
const browser = await puppeteer.launch({
headless: "new",
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-zygote',
'--disable-gpu'
]
});
console.log(`Calculating predicted rating for PDGA ${pdgaNumber}...`);
const predictedRating = await getPredictedRating(browser, pdgaNumber);