Merge branch 'main' of https://git.gnxsoft.com/gnx/GNX-WEB
This commit is contained in:
@@ -12,7 +12,11 @@ import ServicesInitAnimations from "@/components/pages/services/ServicesInitAnim
|
|||||||
import { Service } from "@/lib/api/serviceService";
|
import { Service } from "@/lib/api/serviceService";
|
||||||
import { generateServiceMetadata } from "@/lib/seo/metadata";
|
import { generateServiceMetadata } from "@/lib/seo/metadata";
|
||||||
import { ServiceSchema, BreadcrumbSchema } from "@/components/shared/seo/StructuredData";
|
import { ServiceSchema, BreadcrumbSchema } from "@/components/shared/seo/StructuredData";
|
||||||
|
<<<<<<< HEAD
|
||||||
import { API_CONFIG, getApiHeaders } from "@/lib/config/api";
|
import { API_CONFIG, getApiHeaders } from "@/lib/config/api";
|
||||||
|
=======
|
||||||
|
import { API_CONFIG } from "@/lib/config/api";
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
|
|
||||||
interface ServicePageProps {
|
interface ServicePageProps {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
@@ -24,6 +28,7 @@ interface ServicePageProps {
|
|||||||
// This pre-generates known pages, but new pages can still be generated on-demand
|
// This pre-generates known pages, but new pages can still be generated on-demand
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
try {
|
try {
|
||||||
|
<<<<<<< HEAD
|
||||||
// Use internal API URL for server-side requests
|
// Use internal API URL for server-side requests
|
||||||
const apiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086';
|
const apiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086';
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -31,6 +36,15 @@ export async function generateStaticParams() {
|
|||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: getApiHeaders(),
|
headers: getApiHeaders(),
|
||||||
|
=======
|
||||||
|
const response = await fetch(
|
||||||
|
`${API_CONFIG.BASE_URL}/api/services/`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
next: { revalidate: 60 }, // Revalidate every minute for faster image updates
|
next: { revalidate: 60 }, // Revalidate every minute for faster image updates
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -57,6 +71,7 @@ export async function generateMetadata({ params }: ServicePageProps) {
|
|||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
<<<<<<< HEAD
|
||||||
// Use internal API URL for server-side requests
|
// Use internal API URL for server-side requests
|
||||||
const apiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086';
|
const apiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086';
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -64,6 +79,15 @@ export async function generateMetadata({ params }: ServicePageProps) {
|
|||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: getApiHeaders(),
|
headers: getApiHeaders(),
|
||||||
|
=======
|
||||||
|
const response = await fetch(
|
||||||
|
`${API_CONFIG.BASE_URL}/api/services/${slug}/`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
next: { revalidate: 60 }, // Revalidate every minute for faster image updates
|
next: { revalidate: 60 }, // Revalidate every minute for faster image updates
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -87,6 +111,7 @@ const ServicePage = async ({ params }: ServicePageProps) => {
|
|||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
<<<<<<< HEAD
|
||||||
// Use internal API URL for server-side requests
|
// Use internal API URL for server-side requests
|
||||||
const apiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086';
|
const apiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086';
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -94,6 +119,15 @@ const ServicePage = async ({ params }: ServicePageProps) => {
|
|||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: getApiHeaders(),
|
headers: getApiHeaders(),
|
||||||
|
=======
|
||||||
|
const response = await fetch(
|
||||||
|
`${API_CONFIG.BASE_URL}/api/services/${slug}/`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
next: { revalidate: 60 }, // Revalidate every minute for faster image updates
|
next: { revalidate: 60 }, // Revalidate every minute for faster image updates
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,8 +12,29 @@ type ModalType = 'create' | 'knowledge' | 'status' | null;
|
|||||||
const SupportCenterPage = () => {
|
const SupportCenterPage = () => {
|
||||||
// Set metadata for client component
|
// Set metadata for client component
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
<<<<<<< HEAD
|
||||||
// Only run on client side
|
// Only run on client side
|
||||||
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
||||||
|
=======
|
||||||
|
const metadata = createMetadata({
|
||||||
|
title: "Support Center - Enterprise Support & Help Desk",
|
||||||
|
description: "Get 24/7 enterprise support from GNX Soft. Access our knowledge base, create support tickets, check ticket status, and get help with our software solutions and services.",
|
||||||
|
keywords: [
|
||||||
|
"Support Center",
|
||||||
|
"Customer Support",
|
||||||
|
"Help Desk",
|
||||||
|
"Technical Support",
|
||||||
|
"Knowledge Base",
|
||||||
|
"Support Tickets",
|
||||||
|
"Enterprise Support",
|
||||||
|
"IT Support",
|
||||||
|
],
|
||||||
|
url: "/support-center",
|
||||||
|
});
|
||||||
|
|
||||||
|
const titleString = typeof metadata.title === 'string' ? metadata.title : "Support Center | GNX Soft";
|
||||||
|
document.title = titleString;
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const metadata = createMetadata({
|
const metadata = createMetadata({
|
||||||
|
|||||||
@@ -227,7 +227,11 @@ const AboutBanner = () => {
|
|||||||
|
|
||||||
{/* Social Links */}
|
{/* Social Links */}
|
||||||
<div className="social-links">
|
<div className="social-links">
|
||||||
|
<<<<<<< HEAD
|
||||||
<Link href="https://www.linkedin.com" target="_blank" className="social-link">
|
<Link href="https://www.linkedin.com" target="_blank" className="social-link">
|
||||||
|
=======
|
||||||
|
<Link href="https://linkedin.com" target="_blank" className="social-link">
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
<i className="fa-brands fa-linkedin-in"></i>
|
<i className="fa-brands fa-linkedin-in"></i>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="https://github.com" target="_blank" className="social-link">
|
<Link href="https://github.com" target="_blank" className="social-link">
|
||||||
|
|||||||
@@ -117,7 +117,11 @@ const CareerBanner = () => {
|
|||||||
<ul className="social">
|
<ul className="social">
|
||||||
<li>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
|
<<<<<<< HEAD
|
||||||
href="https://www.linkedin.com"
|
href="https://www.linkedin.com"
|
||||||
|
=======
|
||||||
|
href="https://linkedin.com"
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
target="_blank"
|
target="_blank"
|
||||||
aria-label="connect with us on linkedin"
|
aria-label="connect with us on linkedin"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useCaseStudy } from "@/lib/hooks/useCaseStudy";
|
import { useCaseStudy } from "@/lib/hooks/useCaseStudy";
|
||||||
import { getImageUrl } from "@/lib/imageUtils";
|
import { getImageUrl } from "@/lib/imageUtils";
|
||||||
|
<<<<<<< HEAD
|
||||||
import { sanitizeHTML } from "@/lib/security/sanitize";
|
import { sanitizeHTML } from "@/lib/security/sanitize";
|
||||||
|
=======
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
|
|
||||||
interface CaseSingleProps {
|
interface CaseSingleProps {
|
||||||
slug: string;
|
slug: string;
|
||||||
|
|||||||
@@ -84,7 +84,11 @@ const ServicesBanner = () => {
|
|||||||
<ul className="social">
|
<ul className="social">
|
||||||
<li>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
|
<<<<<<< HEAD
|
||||||
href="https://www.linkedin.com"
|
href="https://www.linkedin.com"
|
||||||
|
=======
|
||||||
|
href="https://linkedin.com"
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
target="_blank"
|
target="_blank"
|
||||||
aria-label="connect with us on linkedin"
|
aria-label="connect with us on linkedin"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -289,7 +289,11 @@ const Footer = () => {
|
|||||||
<div className="col-12 col-lg-6">
|
<div className="col-12 col-lg-6">
|
||||||
<div className="social-links justify-content-center justify-content-lg-end">
|
<div className="social-links justify-content-center justify-content-lg-end">
|
||||||
<Link
|
<Link
|
||||||
|
<<<<<<< HEAD
|
||||||
href="https://www.linkedin.com"
|
href="https://www.linkedin.com"
|
||||||
|
=======
|
||||||
|
href="https://linkedin.com"
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
title="LinkedIn"
|
title="LinkedIn"
|
||||||
@@ -298,7 +302,11 @@ const Footer = () => {
|
|||||||
<i className="fa-brands fa-linkedin-in"></i>
|
<i className="fa-brands fa-linkedin-in"></i>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
|
<<<<<<< HEAD
|
||||||
href="https://github.com/"
|
href="https://github.com/"
|
||||||
|
=======
|
||||||
|
href="https://github.com"
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
title="GitHub"
|
title="GitHub"
|
||||||
|
|||||||
@@ -175,7 +175,11 @@ const OffcanvasMenu = ({
|
|||||||
<ul className="enterprise-social nav-fade">
|
<ul className="enterprise-social nav-fade">
|
||||||
<li>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
|
<<<<<<< HEAD
|
||||||
href="https://www.linkedin.com"
|
href="https://www.linkedin.com"
|
||||||
|
=======
|
||||||
|
href="https://linkedin.com"
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
target="_blank"
|
target="_blank"
|
||||||
aria-label="Connect with us on LinkedIn"
|
aria-label="Connect with us on LinkedIn"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -123,9 +123,13 @@ const nextConfig = {
|
|||||||
// Note: Removed conflicting directives that are ignored with 'strict-dynamic'
|
// Note: Removed conflicting directives that are ignored with 'strict-dynamic'
|
||||||
{
|
{
|
||||||
key: 'Content-Security-Policy',
|
key: 'Content-Security-Policy',
|
||||||
|
<<<<<<< HEAD
|
||||||
value: process.env.NODE_ENV === 'production'
|
value: process.env.NODE_ENV === 'production'
|
||||||
? "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://www.google-analytics.com; frame-src 'self' https://www.google.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests;"
|
? "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://www.google-analytics.com; frame-src 'self' https://www.google.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests;"
|
||||||
: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https: http://localhost:8000 http://localhost:8080; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' http://localhost:8000 https://www.google-analytics.com; frame-src 'self' https://www.google.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self';"
|
: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https: http://localhost:8000 http://localhost:8080; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' http://localhost:8000 https://www.google-analytics.com; frame-src 'self' https://www.google.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self';"
|
||||||
|
=======
|
||||||
|
value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https: http://localhost:8000 http://localhost:8080; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' http://localhost:8000 https://www.google-analytics.com; frame-src 'self' https://www.google.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
},
|
},
|
||||||
// Hide X-Powered-By header from Next.js
|
// Hide X-Powered-By header from Next.js
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,6 +11,14 @@
|
|||||||
# 3. Database: PostgreSQL on host (port 5433 to avoid conflict with Docker instance on 5432)
|
# 3. Database: PostgreSQL on host (port 5433 to avoid conflict with Docker instance on 5432)
|
||||||
# 4. Use install-postgresql.sh to install and configure PostgreSQL
|
# 4. Use install-postgresql.sh to install and configure PostgreSQL
|
||||||
# 5. Use start-services.sh to start both backend and frontend services
|
# 5. Use start-services.sh to start both backend and frontend services
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
#
|
||||||
|
# NOTE: Rate limiting zones must be defined in the main nginx.conf http context
|
||||||
|
# Add these lines to /etc/nginx/nginx.conf inside the http {} block:
|
||||||
|
# limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
||||||
|
# limit_req_zone $binary_remote_addr zone=general_limit:10m rate=100r/s;
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
|
|
||||||
# Frontend - Public facing (Next.js Production Server on port 1087)
|
# Frontend - Public facing (Next.js Production Server on port 1087)
|
||||||
upstream frontend {
|
upstream frontend {
|
||||||
@@ -64,9 +72,7 @@ server {
|
|||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
|
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
|
||||||
|
|
||||||
# Rate Limiting Zones
|
# Rate Limiting (zones must be defined in main nginx.conf http context)
|
||||||
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
|
||||||
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=100r/s;
|
|
||||||
limit_req_status 429;
|
limit_req_status 429;
|
||||||
|
|
||||||
# Client settings
|
# Client settings
|
||||||
@@ -78,7 +84,147 @@ server {
|
|||||||
access_log /var/log/nginx/gnxsoft_access.log;
|
access_log /var/log/nginx/gnxsoft_access.log;
|
||||||
error_log /var/log/nginx/gnxsoft_error.log warn;
|
error_log /var/log/nginx/gnxsoft_error.log warn;
|
||||||
|
|
||||||
# Root location - Frontend (Next.js)
|
# IMPORTANT: More specific location blocks MUST come before location /
|
||||||
|
# Order matters in nginx - longest match wins
|
||||||
|
|
||||||
|
# API Proxy - Frontend talks to backend ONLY through this internal proxy
|
||||||
|
# Backend port 1086 is BLOCKED from internet by firewall
|
||||||
|
location /api/ {
|
||||||
|
limit_req zone=api_limit burst=20 nodelay;
|
||||||
|
|
||||||
|
# Internal proxy to backend (127.0.0.1:1086)
|
||||||
|
# Backend is NOT accessible from public internet
|
||||||
|
proxy_pass http://backend_internal/api/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
# Add internal API key (must match INTERNAL_API_KEY in Django .env)
|
||||||
|
set $api_key "9hZtPwyScigoBAl59Uvcz_9VztSRC6Zt_6L1B2xTM2M";
|
||||||
|
proxy_set_header X-Internal-API-Key $api_key;
|
||||||
|
|
||||||
|
# Backend sees request as coming from localhost
|
||||||
|
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;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
|
||||||
|
# Hide backend server info
|
||||||
|
proxy_hide_header X-Powered-By;
|
||||||
|
proxy_hide_header Server;
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
proxy_connect_timeout 30s;
|
||||||
|
proxy_send_timeout 30s;
|
||||||
|
proxy_read_timeout 30s;
|
||||||
|
|
||||||
|
# CORS headers (if needed)
|
||||||
|
add_header Access-Control-Allow-Origin "https://gnxsoft.com" always;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||||
|
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Internal-API-Key" always;
|
||||||
|
add_header Access-Control-Allow-Credentials "true" always;
|
||||||
|
|
||||||
|
# Handle preflight requests
|
||||||
|
if ($request_method = 'OPTIONS') {
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Media files (served by nginx directly for better performance)
|
||||||
|
location /media/ {
|
||||||
|
alias /var/www/GNX-WEB/backEnd/media/;
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
# Security
|
||||||
|
location ~ \.(php|py|pl|sh)$ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static files (served by nginx directly)
|
||||||
|
location /static/ {
|
||||||
|
alias /var/www/GNX-WEB/backEnd/staticfiles/;
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Next.js image optimization API - must be proxied to Next.js server
|
||||||
|
# Use regex to match /_next/image with query strings
|
||||||
|
location ~ ^/_next/image {
|
||||||
|
proxy_pass http://frontend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
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;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
|
||||||
|
# Preserve query string
|
||||||
|
proxy_pass_request_headers on;
|
||||||
|
|
||||||
|
# Timeouts for image processing
|
||||||
|
proxy_connect_timeout 30s;
|
||||||
|
proxy_send_timeout 30s;
|
||||||
|
proxy_read_timeout 30s;
|
||||||
|
|
||||||
|
# Buffer settings for image processing
|
||||||
|
proxy_buffering on;
|
||||||
|
proxy_buffer_size 4k;
|
||||||
|
proxy_buffers 8 4k;
|
||||||
|
|
||||||
|
# Cache optimized images
|
||||||
|
proxy_cache_valid 200 1d;
|
||||||
|
add_header Cache-Control "public, max-age=86400";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Next.js static files - serve directly from filesystem for better performance
|
||||||
|
location /_next/static/ {
|
||||||
|
alias /var/www/GNX-WEB/frontEnd/.next/static/;
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
# Correct MIME types
|
||||||
|
types {
|
||||||
|
text/css css;
|
||||||
|
application/javascript js;
|
||||||
|
application/json json;
|
||||||
|
font/woff2 woff2;
|
||||||
|
font/woff woff;
|
||||||
|
font/ttf ttf;
|
||||||
|
image/png png;
|
||||||
|
image/jpeg jpg jpeg;
|
||||||
|
image/webp webp;
|
||||||
|
image/svg+xml svg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Frontend public images - use prefix location (must come before root location)
|
||||||
|
location /images/ {
|
||||||
|
alias /var/www/GNX-WEB/frontEnd/public/images/;
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
# Ensure proper MIME types
|
||||||
|
types {
|
||||||
|
image/png png;
|
||||||
|
image/jpeg jpg jpeg;
|
||||||
|
image/gif gif;
|
||||||
|
image/svg+xml svg;
|
||||||
|
image/webp webp;
|
||||||
|
}
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
# Try files, then fallback
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Root location - Frontend (Next.js) - MUST be last
|
||||||
location / {
|
location / {
|
||||||
limit_req zone=general_limit burst=50 nodelay;
|
limit_req zone=general_limit burst=50 nodelay;
|
||||||
|
|
||||||
@@ -98,6 +244,7 @@ server {
|
|||||||
proxy_read_timeout 60s;
|
proxy_read_timeout 60s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
# API Proxy - Frontend talks to backend ONLY through this internal proxy
|
# API Proxy - Frontend talks to backend ONLY through this internal proxy
|
||||||
# Backend port 1086 is BLOCKED from internet by firewall
|
# Backend port 1086 is BLOCKED from internet by firewall
|
||||||
location /api/ {
|
location /api/ {
|
||||||
@@ -173,6 +320,8 @@ server {
|
|||||||
access_log off;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
# Deny access to hidden files
|
# Deny access to hidden files
|
||||||
location ~ /\. {
|
location ~ /\. {
|
||||||
deny all;
|
deny all;
|
||||||
@@ -184,7 +333,11 @@ server {
|
|||||||
location /admin/ {
|
location /admin/ {
|
||||||
# IP restriction is handled by Django middleware
|
# IP restriction is handled by Django middleware
|
||||||
# Add internal API key (must match INTERNAL_API_KEY in Django .env)
|
# Add internal API key (must match INTERNAL_API_KEY in Django .env)
|
||||||
|
<<<<<<< HEAD
|
||||||
set $api_key "PLACEHOLDER_INTERNAL_API_KEY";
|
set $api_key "PLACEHOLDER_INTERNAL_API_KEY";
|
||||||
|
=======
|
||||||
|
set $api_key "9hZtPwyScigoBAl59Uvcz_9VztSRC6Zt_6L1B2xTM2M";
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
proxy_set_header X-Internal-API-Key $api_key;
|
proxy_set_header X-Internal-API-Key $api_key;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
@@ -220,7 +373,10 @@ server {
|
|||||||
# 5. Public internet can ONLY access nginx (ports 80, 443)
|
# 5. Public internet can ONLY access nginx (ports 80, 443)
|
||||||
# 6. All API calls go through nginx proxy (/api/* → 127.0.0.1:1086/api/*)
|
# 6. All API calls go through nginx proxy (/api/* → 127.0.0.1:1086/api/*)
|
||||||
# 7. Backend IP whitelist middleware ensures only localhost requests
|
# 7. Backend IP whitelist middleware ensures only localhost requests
|
||||||
|
<<<<<<< HEAD
|
||||||
# 8. Update PLACEHOLDER_INTERNAL_API_KEY with actual key from Django .env
|
# 8. Update PLACEHOLDER_INTERNAL_API_KEY with actual key from Django .env
|
||||||
|
=======
|
||||||
|
# 8. Rate limiting zones must be added to /etc/nginx/nginx.conf http {} block
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
# 9. PostgreSQL runs on port 5433 (to avoid conflict with Docker on 5432)
|
# 9. PostgreSQL runs on port 5433 (to avoid conflict with Docker on 5432)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
|
|||||||
7
nginx-rate-limit-zones.conf
Normal file
7
nginx-rate-limit-zones.conf
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Rate Limiting Zones for GNX-WEB
|
||||||
|
# Add these lines to /etc/nginx/nginx.conf inside the http {} block
|
||||||
|
# Or include this file in /etc/nginx/nginx.conf with: include /etc/nginx/conf.d/rate-limit-zones.conf;
|
||||||
|
|
||||||
|
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
||||||
|
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=100r/s;
|
||||||
|
|
||||||
@@ -211,10 +211,48 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
# Set PORT environment variable and start with PM2
|
# Set PORT environment variable and start with PM2
|
||||||
PORT=$FRONTEND_PORT NODE_ENV=production pm2 start npm \
|
PORT=$FRONTEND_PORT NODE_ENV=production pm2 start npm \
|
||||||
--name "gnxsoft-frontend" \
|
--name "gnxsoft-frontend" \
|
||||||
-- start
|
-- start
|
||||||
|
=======
|
||||||
|
# Check if Next.js is using standalone output mode
|
||||||
|
if grep -q '"output":\s*"standalone"' next.config.js 2>/dev/null || grep -q "output:.*'standalone'" next.config.js 2>/dev/null; then
|
||||||
|
echo -e "${BLUE}Detected standalone mode. Starting with standalone server...${NC}"
|
||||||
|
|
||||||
|
# Check if standalone server exists
|
||||||
|
if [ ! -f ".next/standalone/server.js" ]; then
|
||||||
|
echo -e "${YELLOW}Standalone server not found. Rebuilding...${NC}"
|
||||||
|
NODE_ENV=production PORT=$FRONTEND_PORT npm run build
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure public folder is copied to standalone directory
|
||||||
|
if [ ! -d ".next/standalone/public" ]; then
|
||||||
|
echo -e "${BLUE}Copying public folder to standalone directory...${NC}"
|
||||||
|
cp -r public .next/standalone/
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure static folder symlink exists for image optimization
|
||||||
|
if [ ! -L ".next/standalone/.next/static" ] && [ ! -d ".next/standalone/.next/static" ]; then
|
||||||
|
echo -e "${BLUE}Creating symlink for static files...${NC}"
|
||||||
|
mkdir -p .next/standalone/.next
|
||||||
|
ln -s ../../static .next/standalone/.next/static
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start standalone server with PM2
|
||||||
|
PORT=$FRONTEND_PORT NODE_ENV=production pm2 start node \
|
||||||
|
--name "gnxsoft-frontend" \
|
||||||
|
--cwd "$FRONTEND_DIR" \
|
||||||
|
-- \
|
||||||
|
".next/standalone/server.js"
|
||||||
|
else
|
||||||
|
# Standard Next.js start
|
||||||
|
PORT=$FRONTEND_PORT NODE_ENV=production pm2 start npm \
|
||||||
|
--name "gnxsoft-frontend" \
|
||||||
|
-- start
|
||||||
|
fi
|
||||||
|
>>>>>>> d7d7a2757a183aa1abd9dbabff804c45298df4e5
|
||||||
|
|
||||||
# Save PM2 configuration
|
# Save PM2 configuration
|
||||||
pm2 save
|
pm2 save
|
||||||
|
|||||||
34
switch-to-sqlite.sh
Normal file
34
switch-to-sqlite.sh
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Switch Django backend to use SQLite instead of PostgreSQL
|
||||||
|
|
||||||
|
BACKEND_DIR="/var/www/GNX-WEB/backEnd"
|
||||||
|
BACKEND_ENV="$BACKEND_DIR/.env"
|
||||||
|
|
||||||
|
echo "Switching to SQLite database..."
|
||||||
|
|
||||||
|
if [ -f "$BACKEND_ENV" ]; then
|
||||||
|
# Comment out or remove DATABASE_URL line
|
||||||
|
if grep -q "^DATABASE_URL=" "$BACKEND_ENV"; then
|
||||||
|
echo "Commenting out DATABASE_URL to use SQLite..."
|
||||||
|
sed -i 's|^DATABASE_URL=.*|# DATABASE_URL= # Using SQLite instead|' "$BACKEND_ENV"
|
||||||
|
echo "✓ DATABASE_URL commented out"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure db.sqlite3 path is correct (it should be in backEnd directory)
|
||||||
|
echo ""
|
||||||
|
echo "SQLite database will be at: $BACKEND_DIR/db.sqlite3"
|
||||||
|
echo ""
|
||||||
|
echo "Restarting backend to apply changes..."
|
||||||
|
pm2 restart gnxsoft-backend
|
||||||
|
echo "✓ Backend restarted"
|
||||||
|
echo ""
|
||||||
|
echo "Checking database connection..."
|
||||||
|
cd "$BACKEND_DIR"
|
||||||
|
source venv/bin/activate
|
||||||
|
python manage.py check --database default
|
||||||
|
else
|
||||||
|
echo "Error: .env file not found at $BACKEND_ENV"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
Reference in New Issue
Block a user