diff --git a/Dockerfile b/Dockerfile index 2d51f81..5848ff8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM node:20-alpine as build WORKDIR /app # Copy package files -COPY package*.json ./ +COPY package.json package-lock.json ./ # Install dependencies RUN npm ci diff --git a/backend/Dockerfile b/backend/Dockerfile index 72df894..d44659e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /app COPY package*.json ./ # Install dependencies -RUN npm ci --only=production +RUN npm install --only=production # Copy source code COPY . . diff --git a/backend/src/index.js b/backend/src/index.js index c72c45d..811f4f3 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -11,18 +11,26 @@ dotenv.config(); const app = express(); const port = process.env.PORT || 3001; +// Trust proxy for rate limiting behind Traefik +app.set('trust proxy', 1); + // Middleware app.use(helmet()); app.use(express.json()); app.use(cors({ - origin: process.env.CORS_ORIGIN, + origin: process.env.CORS_ORIGIN || 'https://knck.pl', methods: ['POST'] })); // Rate limiting const limiter = rateLimit({ windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 900000, // 15 minutes - max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 5 // 5 requests per window + max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 5, // 5 requests per window + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers + keyGenerator: (req) => { + return req.ip || req.socket.remoteAddress; + } }); app.use('/api/contact', limiter); @@ -30,10 +38,34 @@ app.use('/api/contact', limiter); const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT), - secure: false, + secure: false, // true for 465, false for other ports like 587 auth: { + type: 'login', user: process.env.SMTP_USER, pass: process.env.SMTP_PASS + }, + tls: { + rejectUnauthorized: false // Required for some SMTP servers + }, + debug: true, // Enable debug logging + logger: true // Enable logger +}); + +// Verify SMTP connection configuration +transporter.verify(function(error, success) { + if (error) { + console.error('SMTP connection error:', { + message: error.message, + code: error.code, + response: error.response, + command: error.command, + stack: error.stack, + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT, + user: process.env.SMTP_USER + }); + } else { + console.log('SMTP server is ready to take our messages'); } }); @@ -73,7 +105,13 @@ ${message} res.status(200).json({ message: 'Email sent successfully' }); } catch (error) { - console.error('Error sending email:', error); + console.error('Error sending email:', { + message: error.message, + code: error.code, + response: error.response, + command: error.command, + stack: error.stack + }); res.status(500).json({ error: 'Failed to send email' }); } }); diff --git a/combined.yaml b/combined.yaml new file mode 100644 index 0000000..cbfe89a --- /dev/null +++ b/combined.yaml @@ -0,0 +1,160 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: knck-app + labels: + app: knck-app +spec: + replicas: 2 + selector: + matchLabels: + app: knck-app + template: + metadata: + labels: + app: knck-app + spec: + containers: + - name: knck-frontend + image: knck-frontend:latest + imagePullPolicy: Always + ports: + - containerPort: 80 + resources: + requests: + cpu: "50m" + memory: "64Mi" + limits: + cpu: "100m" + memory: "128Mi" + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 15 + periodSeconds: 20 + - name: knck-backend + image: knck-backend:latest + imagePullPolicy: Always + ports: + - containerPort: 3001 + env: + - name: PORT + value: "3001" + - name: NODE_ENV + value: "production" + - name: SMTP_HOST + valueFrom: + secretKeyRef: + name: knck-secrets + key: smtp-host + - name: SMTP_PORT + valueFrom: + secretKeyRef: + name: knck-secrets + key: smtp-port + - name: SMTP_USER + valueFrom: + secretKeyRef: + name: knck-secrets + key: smtp-user + - name: SMTP_PASS + valueFrom: + secretKeyRef: + name: knck-secrets + key: smtp-pass + - name: SMTP_FROM + valueFrom: + secretKeyRef: + name: knck-secrets + key: smtp-from + - name: SMTP_TO + valueFrom: + secretKeyRef: + name: knck-secrets + key: smtp-to + - name: CORS_ORIGIN + value: "https://knck.pl" + resources: + requests: + cpu: "100m" + memory: "128Mi" + limits: + cpu: "200m" + memory: "256Mi" + readinessProbe: + httpGet: + path: /health + port: 3001 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: 3001 + initialDelaySeconds: 15 + periodSeconds: 20 +--- +kind: Ingress +metadata: + name: knck-ingress + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd + traefik.ingress.kubernetes.io/service.serversscheme: http + traefik.ingress.kubernetes.io/service.passhostheader: "true" + traefik.ingress.kubernetes.io/router.priority: "1" + cert-manager.io/cluster-issuer: letsencrypt-prod + acme.cert-manager.io/http01-edit-in-place: "true" + acme.cert-manager.io/http01-ingress-class: traefik +spec: + tls: + - hosts: + - knck.pl + - api.knck.pl + secretName: knck-tls + rules: + - host: knck.pl + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: knck-app + port: + number: 80 + - host: api.knck.pl + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: knck-app + port: + number: 3001 +--- +kind: Service +metadata: + name: knck-app +spec: + selector: + app: knck-app + ports: + - name: frontend + protocol: TCP + port: 80 + targetPort: 80 + - name: backend + protocol: TCP + port: 3001 + targetPort: 3001 + type: ClusterIP \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..49e17eb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3.8' + +services: + frontend: + build: + context: . + dockerfile: Dockerfile + ports: + - "80:80" + depends_on: + - backend + + backend: + build: + context: ./backend + dockerfile: Dockerfile + ports: + - "3001:3001" + environment: + - SMTP_HOST=smtp.mail.me.com + - SMTP_PORT=587 + - SMTP_USER=your-email@me.com + - SMTP_PASS=your-app-specific-password + - SMTP_FROM=your-email@me.com + - SMTP_TO=your-email@me.com + - CORS_ORIGIN=http://localhost \ No newline at end of file diff --git a/k8s/combined-deployment.yaml b/k8s/combined-deployment.yaml index 15b99f6..1ed64ee 100644 --- a/k8s/combined-deployment.yaml +++ b/k8s/combined-deployment.yaml @@ -79,8 +79,6 @@ spec: secretKeyRef: name: knck-secrets key: smtp-to - - name: CORS_ORIGIN - value: "https://knck.pl" resources: requests: cpu: "100m" diff --git a/k8s/combined-ingress.yaml b/k8s/combined-ingress.yaml index 991a1e6..13fe691 100644 --- a/k8s/combined-ingress.yaml +++ b/k8s/combined-ingress.yaml @@ -16,7 +16,6 @@ spec: tls: - hosts: - knck.pl - - api.knck.pl secretName: knck-tls rules: - host: knck.pl @@ -29,10 +28,7 @@ spec: name: knck-app port: number: 80 - - host: api.knck.pl - http: - paths: - - path: / + - path: /api pathType: Prefix backend: service: diff --git a/k8s/secrets.yaml b/k8s/secrets.yaml index b5ff575..6cd459c 100644 --- a/k8s/secrets.yaml +++ b/k8s/secrets.yaml @@ -4,10 +4,11 @@ metadata: name: knck-secrets type: Opaque data: - # These values should be base64 encoded - smtp-host: c210cC5tYWlsLm1lLmNvbQ== # smtp.mail.me.com - smtp-port: NTg3 # 587 - smtp-user: eW91ci1lbWFpbEBtZS5jb20= # your-email@me.com - smtp-pass: eW91ci1hcHAtc3BlY2lmaWMtcGFzc3dvcmQ= # your-app-specific-password - smtp-from: eW91ci1lbWFpbEBtZS5jb20= # your-email@me.com - smtp-to: eW91ci1lbWFpbEBtZS5jb20= # your-email@me.com \ No newline at end of file + # Replace these values with your base64 encoded actual values + # Example: echo -n "your-actual-value" | base64 + smtp-host: REPLACE_WITH_YOUR_BASE64_ENCODED_SMTP_HOST + smtp-port: REPLACE_WITH_YOUR_BASE64_ENCODED_SMTP_PORT + smtp-user: REPLACE_WITH_YOUR_BASE64_ENCODED_SMTP_USER + smtp-pass: REPLACE_WITH_YOUR_BASE64_ENCODED_SMTP_PASSWORD + smtp-from: REPLACE_WITH_YOUR_BASE64_ENCODED_SMTP_FROM + smtp-to: REPLACE_WITH_YOUR_BASE64_ENCODED_SMTP_TO \ No newline at end of file diff --git a/src/components/LandingPage.tsx b/src/components/LandingPage.tsx index 9fc8f00..0ba012b 100644 --- a/src/components/LandingPage.tsx +++ b/src/components/LandingPage.tsx @@ -23,13 +23,13 @@ const LandingPage: FC = () => { Jakub Kaniecki
- + - + - +
@@ -108,7 +108,7 @@ const LandingPage: FC = () => { ); diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx index c879a48..80b6c83 100644 --- a/src/pages/Blog.tsx +++ b/src/pages/Blog.tsx @@ -25,7 +25,6 @@ interface GroupedPosts { const Blog: FC = () => { const { slug } = useParams<{ slug: string }>(); - const [posts, setPosts] = useState([]); const [groupedPosts, setGroupedPosts] = useState({}); const [currentPost, setCurrentPost] = useState(null); const [loading, setLoading] = useState(true); @@ -66,8 +65,6 @@ const Blog: FC = () => { const sortedPosts = loadedPosts.sort( (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() ); - - setPosts(sortedPosts); // Group posts by year and month const grouped: GroupedPosts = {}; diff --git a/src/pages/Contact.tsx b/src/pages/Contact.tsx index 427fa8c..ece0641 100644 --- a/src/pages/Contact.tsx +++ b/src/pages/Contact.tsx @@ -19,7 +19,7 @@ const Contact: FC = () => { setStatus({ type: null, message: '' }); try { - const response = await fetch('https://api.knck.pl/api/contact', { + const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json',