diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..c739ace --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,62 @@ +name: Build and Deploy to Production +run-name: Deploying commit ${{ gitea.sha_short }} by @${{ gitea.actor }} +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: node-pm2-runner + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Create Backend .env file + run: | + echo "Creating backend .env file..." + cd backend + # Using both Gitea Variables (vars) and Secrets (secrets) + echo "BACKEND_PORT=${{ vars.BACKEND_PORT }}" >> .env + echo "FRONTEND_URL=${{ vars.FRONTEND_URL }}" >> .env + echo "SMTP_HOST=${{ vars.SMTP_HOST }}" >> .env + echo "SMTP_PORT=${{ vars.SMTP_PORT }}" >> .env + echo "SMTP_SECURE=${{ vars.SMTP_SECURE }}" >> .env + echo "YOUR_RECEIVING_EMAIL=${{ vars.YOUR_RECEIVING_EMAIL }}" >> .env + echo "SMTP_FROM_EMAIL=${{ vars.SMTP_FROM_EMAIL }}" >> .env + + # Secrets should be used for sensitive data + echo "SMTP_USER=${{ secrets.SMTP_USER }}" >> .env + echo "SMTP_PASS=${{ secrets.SMTP_PASS }}" >> .env + + + - name: Create Frontend .env.local file + run: | + echo "Creating frontend .env.local file..." + cd frontend + # The BACKEND_URL is not needed because Traefik will handle routing /api + echo "NEXT_PUBLIC_GITHUB_URL=${{ vars.NEXT_PUBLIC_GITHUB_URL }}" >> .env.local + echo "NEXT_PUBLIC_LINKEDIN_URL=${{ vars.NEXT_PUBLIC_LINKEDIN_URL }}" >> .env.local + echo "NEXT_PUBLIC_TWITTER_URL=${{ vars.NEXT_PUBLIC_TWITTER_URL }}" >> .env.local + + - name: Install Dependencies and Build + run: | + echo "Installing backend dependencies..." + cd backend && npm install && cd .. + echo "Installing frontend dependencies..." + cd frontend && npm install + echo "Building frontend application..." + npm run build + + - name: Restart Applications with PM2 + run: | + echo "Restarting applications with PM2..." + pm2 startOrRestart backend/ecosystem.config.js --update-env + pm2 startOrRestart frontend/ecosystem.config.js + echo "Deployment successful!" \ No newline at end of file diff --git a/backend/ecosystem.config.js b/backend/ecosystem.config.js new file mode 100644 index 0000000..147403c --- /dev/null +++ b/backend/ecosystem.config.js @@ -0,0 +1,13 @@ +module.exports = { + apps: [{ + name: 'portfolio-backend', // A name for your application in PM2 + script: 'server.js', // The path to the script PM2 will run + instances: 1, // Run a single instance of the application + autorestart: true, // Automatically restart if the app crashes + watch: false, // Do NOT watch for file changes in production + max_memory_restart: '256M', // Restart if it uses more than 256MB of RAM + env: { + NODE_ENV: 'production', // Set the environment to production + } + }] +}; \ No newline at end of file diff --git a/frontend/ecosystem.config.js b/frontend/ecosystem.config.js new file mode 100644 index 0000000..a71d1dc --- /dev/null +++ b/frontend/ecosystem.config.js @@ -0,0 +1,14 @@ +module.exports = { + apps: [{ + name: 'portfolio-frontend', // A name for your application in PM2 + script: 'npm', // Tell PM2 to run an npm command + args: 'start', // The argument to pass to npm, which runs 'next start' + instances: 1, // Run a single instance of the application + autorestart: true, // Automatically restart if the app crashes + watch: false, // Do NOT watch for file changes in production + max_memory_restart: '512M', // Restart if it uses more than 512MB of RAM + env: { + NODE_ENV: 'production', // Set the environment to production + } + }] +}; \ No newline at end of file diff --git a/frontend/src/app/components/sections/Contact.tsx b/frontend/src/app/components/sections/Contact.tsx index 00a13fa..4ec8057 100644 --- a/frontend/src/app/components/sections/Contact.tsx +++ b/frontend/src/app/components/sections/Contact.tsx @@ -8,7 +8,6 @@ export default function Contact() { const t = useTranslations('contact'); const [formData, setFormData] = useState<{ name: string; email: string; message: string }>({ name: '', email: '', message: '' }); const [isSubmitting, setIsSubmitting] = useState(false); - const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:3001'; const handleChange = (e: React.ChangeEvent) => { setFormData({...formData, [e.target.name]: e.target.value }); @@ -31,7 +30,7 @@ export default function Contact() { const submissionPromise = async () => { try { - const response = await fetch(`${backendUrl}/api/email/send`, { + const response = await fetch(`/api/email/send`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData),