Compare commits

...

6 Commits

Author SHA1 Message Date
fa71f53cb9 Add note: I was in fact here - June 21, 2025 2025-06-21 04:29:34 +02:00
613ffe8940 Remove temporary note - restore original README 2025-06-21 04:28:48 +02:00
ff823fd72e Update README.md - I was here June 21, 2025 2025-06-21 04:28:03 +02:00
ef420a777b Update docker-compose.yml 2025-06-20 00:17:18 +02:00
27a2ce6c40 Update docker-compose.yml 2025-06-20 00:15:50 +02:00
v0
da55b8b7dd fix: remove invalid <config> element in app/page.tsx
Replace incorrect React component with valid one.

#VERCEL_SKIP

Co-authored-by: Anders Lehmann Pier <3219386+AndersPier@users.noreply.github.com>
2025-06-19 22:07:32 +00:00
12 changed files with 2244 additions and 52 deletions

51
.dockerignore Normal file
View File

@@ -0,0 +1,51 @@
# Dependencies
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Next.js
.next/
out/
# Production
build
# Misc
.DS_Store
*.tsbuildinfo
# Debug
*.log
# Local env files
.env*.local
.env
# Vercel
.vercel
# TypeScript
*.tsbuildinfo
next-env.d.ts
# IDE
.vscode
.idea
# Git
.git
.gitignore
README.md
# Docker
Dockerfile*
docker-compose*
.dockerignore
# Testing
coverage
.nyc_output
# Linting
.eslintcache

69
Dockerfile Normal file
View File

@@ -0,0 +1,69 @@
# Use the official Node.js 18 Alpine image as base
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
# set hostname to localhost
ENV HOSTNAME "0.0.0.0"
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD ["node", "server.js"]

54
Makefile Normal file
View File

@@ -0,0 +1,54 @@
# Makefile for Markdown Editor Docker operations
.PHONY: help build run stop clean logs shell health
# Default target
help:
@echo "Available commands:"
@echo " build - Build the Docker image"
@echo " run - Run the application with docker-compose"
@echo " run-prod - Run with nginx reverse proxy"
@echo " stop - Stop all containers"
@echo " clean - Remove containers and images"
@echo " logs - Show application logs"
@echo " shell - Open shell in the app container"
@echo " health - Check application health"
# Build the Docker image
build:
docker-compose build --no-cache
# Run the application (development)
run:
docker-compose up -d markdown-editor
@echo "Application is running at http://localhost:3000"
# Run with production setup (nginx + app)
run-prod:
docker-compose --profile production up -d
@echo "Application is running at http://localhost (port 80)"
# Stop all containers
stop:
docker-compose down
# Clean up containers and images
clean:
docker-compose down --rmi all --volumes --remove-orphans
docker system prune -f
# Show logs
logs:
docker-compose logs -f markdown-editor
# Open shell in the app container
shell:
docker-compose exec markdown-editor sh
# Check application health
health:
@echo "Checking application health..."
@curl -f http://localhost:3000/api/health || echo "Health check failed"
# Quick development cycle
dev: stop build run logs

View File

@@ -28,3 +28,6 @@ Continue building your app on:
2. Deploy your chats from the v0 interface 2. Deploy your chats from the v0 interface
3. Changes are automatically pushed to this repository 3. Changes are automatically pushed to this repository
4. Vercel deploys the latest version from this repository 4. Vercel deploys the latest version from this repository
I was in fact here - June 21, 2025

12
app/api/health/route.ts Normal file
View File

@@ -0,0 +1,12 @@
import { NextResponse } from "next/server"
export async function GET() {
return NextResponse.json(
{
status: "healthy",
timestamp: new Date().toISOString(),
service: "markdown-editor",
},
{ status: 200 },
)
}

View File

@@ -1,7 +1,9 @@
"use client" // app/page.tsx
export default function HomePage() {
import config from "../tailwind.config" return (
<main className="flex min-h-screen flex-col items-center justify-center gap-4">
export default function SyntheticV0PageForDeployment() { <h1 className="text-3xl font-bold">Markdown Editor</h1>
return <config /> <p className="text-muted-foreground">Your application is up and running. Start editing!</p>
</main>
)
} }

View File

@@ -25,10 +25,10 @@ services:
container_name: markdown-editor-nginx container_name: markdown-editor-nginx
ports: ports:
- "80:80" - "80:80"
- "443:443" #- "443:443"
volumes: volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro #- ./ssl:/etc/nginx/ssl:ro
depends_on: depends_on:
- markdown-editor - markdown-editor
restart: unless-stopped restart: unless-stopped

96
nginx.conf Normal file
View File

@@ -0,0 +1,96 @@
events {
worker_connections 1024;
}
http {
upstream markdown-editor {
server markdown-editor:3000;
}
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
listen 80;
server_name localhost;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Static files caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
proxy_pass http://markdown-editor;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# API routes with rate limiting
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://markdown-editor;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Main application
location / {
proxy_pass http://markdown-editor;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (if needed)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
# HTTPS configuration (uncomment and configure for production)
# server {
# listen 443 ssl http2;
# server_name your-domain.com;
#
# ssl_certificate /etc/nginx/ssl/cert.pem;
# ssl_certificate_key /etc/nginx/ssl/key.pem;
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
# ssl_prefer_server_ciphers off;
#
# # Same location blocks as above
# }
}

View File

@@ -9,19 +9,54 @@
"start": "next start" "start": "next start"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-accordion": "latest",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-alert-dialog": "latest",
"@radix-ui/react-aspect-ratio": "latest",
"@radix-ui/react-avatar": "latest",
"@radix-ui/react-checkbox": "latest",
"@radix-ui/react-collapsible": "latest",
"@radix-ui/react-context-menu": "latest",
"@radix-ui/react-dialog": "latest",
"@radix-ui/react-dropdown-menu": "latest",
"@radix-ui/react-hover-card": "latest",
"@radix-ui/react-label": "latest",
"@radix-ui/react-menubar": "latest",
"@radix-ui/react-navigation-menu": "latest",
"@radix-ui/react-popover": "latest",
"@radix-ui/react-progress": "latest",
"@radix-ui/react-radio-group": "latest",
"@radix-ui/react-scroll-area": "latest",
"@radix-ui/react-select": "latest",
"@radix-ui/react-separator": "latest",
"@radix-ui/react-slider": "latest",
"@radix-ui/react-slot": "latest",
"@radix-ui/react-switch": "latest",
"@radix-ui/react-tabs": "latest",
"@radix-ui/react-toast": "latest",
"@radix-ui/react-toggle": "latest",
"@radix-ui/react-toggle-group": "latest",
"@radix-ui/react-tooltip": "latest",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "latest",
"embla-carousel-react": "latest",
"input-otp": "latest",
"lucide-react": "^0.454.0", "lucide-react": "^0.454.0",
"next": "15.2.4", "next": "15.2.4",
"next-themes": "latest",
"prism-react-renderer": "^2.3.1", "prism-react-renderer": "^2.3.1",
"react": "^19", "react": "^19",
"react-day-picker": "latest",
"react-dom": "^19", "react-dom": "^19",
"react-hook-form": "latest",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-resizable-panels": "latest",
"recharts": "latest",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.0",
"sonner": "latest",
"tailwind-merge": "^2.5.5", "tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7",
"vaul": "latest"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22", "@types/node": "^22",

1946
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff