chore: update Dockerfile for pnpm, modify package.json dependencies, and implement rate limiting
- Switched from npm to pnpm for dependency management in Dockerfile, improving build efficiency. - Updated Vue and Vue Router versions in package.json for better compatibility. - Changed placeholder URLs in site.ts to actual Fiverr links and adjusted review count. - Removed obsolete sitemap.xml file to streamline the project. - Added a new rate limiting plugin to manage API request limits for the contact endpoint.
This commit is contained in:
+16
-4
@@ -1,17 +1,29 @@
|
|||||||
# Stage 1: Build
|
# Stage 1: Build
|
||||||
FROM node:22-alpine AS builder
|
FROM node:22-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm ci
|
# Install pnpm via corepack
|
||||||
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
|
# Copy manifests first for layer caching
|
||||||
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
# Install all dependencies (including devDeps needed for build)
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy source and build
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN pnpm build
|
||||||
|
|
||||||
# Stage 2: Runtime
|
# Stage 2: Runtime
|
||||||
FROM node:22-alpine AS runner
|
FROM node:22-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
WORKDIR /app
|
|
||||||
|
# Nuxt SSR bundles all server deps into .output/server/
|
||||||
COPY --from=builder /app/.output /app/.output
|
COPY --from=builder /app/.output /app/.output
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD ["node", "/app/.output/server/index.mjs"]
|
CMD ["node", "/app/.output/server/index.mjs"]
|
||||||
|
|||||||
+3
-3
@@ -58,13 +58,13 @@ export const siteConfig: SiteConfig = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'telegram-bot',
|
id: 'telegram-bot',
|
||||||
url: '#',
|
url: 'https://www.fiverr.com/users/mr_kayjaydee',
|
||||||
image: '/images/fiverr/telegram_bot.webp',
|
image: '/images/fiverr/telegram_bot.webp',
|
||||||
price: '$20',
|
price: '$20',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'website-development',
|
id: 'website-development',
|
||||||
url: '#',
|
url: 'https://www.fiverr.com/users/mr_kayjaydee',
|
||||||
image: '/images/fiverr/website.webp',
|
image: '/images/fiverr/website.webp',
|
||||||
price: '$50',
|
price: '$50',
|
||||||
},
|
},
|
||||||
@@ -96,7 +96,7 @@ export const siteConfig: SiteConfig = {
|
|||||||
priceRange: '$$$',
|
priceRange: '$$$',
|
||||||
aggregateRating: {
|
aggregateRating: {
|
||||||
ratingValue: '5',
|
ratingValue: '5',
|
||||||
reviewCount: '50',
|
reviewCount: '10',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-2
@@ -21,8 +21,8 @@
|
|||||||
"nodemailer": "^8.0.5",
|
"nodemailer": "^8.0.5",
|
||||||
"nuxt": "^4.0.0",
|
"nuxt": "^4.0.0",
|
||||||
"nuxt-gtag": "^4.1.0",
|
"nuxt-gtag": "^4.1.0",
|
||||||
"vue": "latest",
|
"vue": "^3.5.0",
|
||||||
"vue-router": "latest",
|
"vue-router": "^4.5.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
|
||||||
|
|
||||||
<!-- Home Page -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Fiverr Services Page -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/fiverr</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Projects Portfolio Page -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/projects</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Contact Page -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/contact</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- About Page -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/about</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Formation/Training Page -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/formation</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Dynamic Project Detail Pages -->
|
|
||||||
|
|
||||||
<!-- Virtual Tour Project -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/project/virtual-tour</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Xinko Discord Bot Project -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/project/xinko</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Image Manipulation NPM Package -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/project/image-manipulation</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Flowboard Project Management App -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/project/flowboard</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Primate Web Admin Project -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/project/primate-web-admin</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Instagram Bot Project -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/project/instagram-bot</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
<!-- Crowdin Status Bot Project -->
|
|
||||||
<url>
|
|
||||||
<loc>https://killiandalcin.fr/project/crowdin-status-bot</loc>
|
|
||||||
<lastmod>2025-07-07</lastmod>
|
|
||||||
</url>
|
|
||||||
|
|
||||||
</urlset>
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
const ipMap = new Map<string, { count: number; reset: number }>()
|
||||||
|
|
||||||
|
// Clean stale entries every 5 minutes to prevent memory leak
|
||||||
|
setInterval(() => {
|
||||||
|
const now = Date.now()
|
||||||
|
for (const [ip, entry] of ipMap) {
|
||||||
|
if (entry.reset < now) ipMap.delete(ip)
|
||||||
|
}
|
||||||
|
}, 5 * 60 * 1000)
|
||||||
|
|
||||||
|
export default defineNitroPlugin((nitro) => {
|
||||||
|
nitro.hooks.hook('request', (event) => {
|
||||||
|
// Only rate-limit the contact POST endpoint
|
||||||
|
if (event.method !== 'POST' || !event.path.startsWith('/api/contact')) return
|
||||||
|
|
||||||
|
const ip = getRequestIP(event, { xForwardedFor: true }) ?? 'unknown'
|
||||||
|
const now = Date.now()
|
||||||
|
const window = 60_000 // 1 minute window
|
||||||
|
const limit = 3 // max 3 requests per minute per IP
|
||||||
|
|
||||||
|
const entry = ipMap.get(ip)
|
||||||
|
if (!entry || entry.reset < now) {
|
||||||
|
ipMap.set(ip, { count: 1, reset: now + window })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.count++
|
||||||
|
if (entry.count > limit) {
|
||||||
|
throw createError({ statusCode: 429, message: 'Too many requests. Please try again later.' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user