Compare commits
6 Commits
cbb95a38cf
...
v1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| a31202f63b | |||
| 0afd6bbb60 | |||
| 2c647f0284 | |||
| d9ff6d640d | |||
| 8ab9ec7d1f | |||
| 0cc67d54ef |
@@ -51,12 +51,25 @@ jobs:
|
|||||||
id: determine
|
id: determine
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ github.ref_name }}"
|
REF="${{ github.ref }}"
|
||||||
|
REF_NAME="${{ github.ref_name }}"
|
||||||
|
REF_TYPE="${{ github.ref_type }}"
|
||||||
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||||
DOMAIN_BASE="mb-grid-solutions.com"
|
DOMAIN_BASE="mb-grid-solutions.com"
|
||||||
PRJ_ID="mb-grid-solutions"
|
PRJ_ID="mb-grid-solutions"
|
||||||
|
|
||||||
if [[ "${{ github.ref_type }}" == "branch" && "$TAG" == "main" ]]; then
|
echo "Detecting environment for ref: $REF ($REF_NAME, type: $REF_TYPE)"
|
||||||
|
|
||||||
|
# Fallback for REF_TYPE if missing
|
||||||
|
if [[ -z "$REF_TYPE" ]]; then
|
||||||
|
if [[ "$REF" == refs/tags/* ]]; then
|
||||||
|
REF_TYPE="tag"
|
||||||
|
elif [[ "$REF" == refs/heads/* ]]; then
|
||||||
|
REF_TYPE="branch"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$REF_TYPE" == "branch" && "$REF_NAME" == "main" ]]; then
|
||||||
TARGET="testing"
|
TARGET="testing"
|
||||||
IMAGE_TAG="testing-${SHORT_SHA}"
|
IMAGE_TAG="testing-${SHORT_SHA}"
|
||||||
ENV_FILE=".env.testing"
|
ENV_FILE=".env.testing"
|
||||||
@@ -64,18 +77,18 @@ jobs:
|
|||||||
NEXT_PUBLIC_BASE_URL="https://testing.${DOMAIN_BASE}"
|
NEXT_PUBLIC_BASE_URL="https://testing.${DOMAIN_BASE}"
|
||||||
DIRECTUS_URL="https://cms.testing.${DOMAIN_BASE}"
|
DIRECTUS_URL="https://cms.testing.${DOMAIN_BASE}"
|
||||||
DIRECTUS_HOST="cms.testing.${DOMAIN_BASE}"
|
DIRECTUS_HOST="cms.testing.${DOMAIN_BASE}"
|
||||||
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
|
elif [[ "$REF_TYPE" == "tag" ]]; then
|
||||||
if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
if [[ "$REF_NAME" =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
|
||||||
TARGET="production"
|
TARGET="production"
|
||||||
IMAGE_TAG="$TAG"
|
IMAGE_TAG="$REF_NAME"
|
||||||
ENV_FILE=".env.prod"
|
ENV_FILE=".env.prod"
|
||||||
TRAEFIK_HOST="${DOMAIN_BASE}, www.${DOMAIN_BASE}"
|
TRAEFIK_HOST="${DOMAIN_BASE}, www.${DOMAIN_BASE}"
|
||||||
NEXT_PUBLIC_BASE_URL="https://${DOMAIN_BASE}"
|
NEXT_PUBLIC_BASE_URL="https://${DOMAIN_BASE}"
|
||||||
DIRECTUS_URL="https://cms.${DOMAIN_BASE}"
|
DIRECTUS_URL="https://cms.${DOMAIN_BASE}"
|
||||||
DIRECTUS_HOST="cms.${DOMAIN_BASE}"
|
DIRECTUS_HOST="cms.${DOMAIN_BASE}"
|
||||||
elif [[ "$TAG" =~ -rc || "$TAG" =~ -beta || "$TAG" =~ -alpha ]]; then
|
elif [[ "$REF_NAME" =~ -rc || "$REF_NAME" =~ -beta || "$REF_NAME" =~ -alpha ]]; then
|
||||||
TARGET="staging"
|
TARGET="staging"
|
||||||
IMAGE_TAG="$TAG"
|
IMAGE_TAG="$REF_NAME"
|
||||||
ENV_FILE=".env.staging"
|
ENV_FILE=".env.staging"
|
||||||
TRAEFIK_HOST="staging.${DOMAIN_BASE}"
|
TRAEFIK_HOST="staging.${DOMAIN_BASE}"
|
||||||
NEXT_PUBLIC_BASE_URL="https://staging.${DOMAIN_BASE}"
|
NEXT_PUBLIC_BASE_URL="https://staging.${DOMAIN_BASE}"
|
||||||
@@ -83,12 +96,16 @@ jobs:
|
|||||||
DIRECTUS_HOST="cms.staging.${DOMAIN_BASE}"
|
DIRECTUS_HOST="cms.staging.${DOMAIN_BASE}"
|
||||||
else
|
else
|
||||||
TARGET="skip"
|
TARGET="skip"
|
||||||
|
echo "Tag $REF_NAME did not match any environment pattern."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
TARGET="skip"
|
TARGET="skip"
|
||||||
|
echo "Ref type $REF_TYPE is not handled for deployment."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Target determined: $TARGET"
|
echo "Target determined: $TARGET"
|
||||||
|
echo "Image tag: $IMAGE_TAG"
|
||||||
|
|
||||||
echo "target=$TARGET" >> "$GITHUB_OUTPUT"
|
echo "target=$TARGET" >> "$GITHUB_OUTPUT"
|
||||||
echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
|
echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
|
||||||
echo "env_file=$ENV_FILE" >> "$GITHUB_OUTPUT"
|
echo "env_file=$ENV_FILE" >> "$GITHUB_OUTPUT"
|
||||||
@@ -195,6 +212,7 @@ jobs:
|
|||||||
# Directus
|
# Directus
|
||||||
DIRECTUS_URL=${{ needs.prepare.outputs.directus_url }}
|
DIRECTUS_URL=${{ needs.prepare.outputs.directus_url }}
|
||||||
DIRECTUS_HOST=${{ needs.prepare.outputs.directus_host }}
|
DIRECTUS_HOST=${{ needs.prepare.outputs.directus_host }}
|
||||||
|
INTERNAL_DIRECTUS_URL=http://directus:8055
|
||||||
DIRECTUS_API_TOKEN=${{ secrets.DIRECTUS_API_TOKEN || vars.DIRECTUS_API_TOKEN }}
|
DIRECTUS_API_TOKEN=${{ secrets.DIRECTUS_API_TOKEN || vars.DIRECTUS_API_TOKEN }}
|
||||||
DIRECTUS_ADMIN_EMAIL=${{ secrets.DIRECTUS_ADMIN_EMAIL || vars.DIRECTUS_ADMIN_EMAIL || 'admin@mintel.me' }}
|
DIRECTUS_ADMIN_EMAIL=${{ secrets.DIRECTUS_ADMIN_EMAIL || vars.DIRECTUS_ADMIN_EMAIL || 'admin@mintel.me' }}
|
||||||
DIRECTUS_ADMIN_PASSWORD=${{ secrets.DIRECTUS_ADMIN_PASSWORD || vars.DIRECTUS_ADMIN_PASSWORD }}
|
DIRECTUS_ADMIN_PASSWORD=${{ secrets.DIRECTUS_ADMIN_PASSWORD || vars.DIRECTUS_ADMIN_PASSWORD }}
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
className="fixed top-0 left-0 right-0 z-[100]"
|
className="fixed top-0 left-0 right-0 z-[100]"
|
||||||
>
|
>
|
||||||
<header
|
<header
|
||||||
className={`transition-all duration-300 flex items-center py-1 ${
|
className={`transition-all duration-300 flex items-center ${
|
||||||
isScrolled
|
isScrolled
|
||||||
? "bg-white/90 backdrop-blur-lg border-b border-slate-200 shadow-sm"
|
? "bg-white/90 backdrop-blur-lg border-b border-slate-200 shadow-sm py-2"
|
||||||
: "bg-gradient-to-b from-white/80 via-white/40 to-transparent"
|
: "bg-gradient-to-b from-white/80 via-white/40 to-transparent py-4"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="container-custom flex justify-between items-center w-full relative z-10">
|
<div className="container-custom flex justify-between items-center w-full relative z-10">
|
||||||
@@ -65,7 +65,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
aria-label={`${t("nav.home")} - Zur Startseite`}
|
aria-label={`${t("nav.home")} - Zur Startseite`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`relative transition-all duration-300 ${isScrolled ? "h-[50px] md:h-[80px] w-[120px] md:w-[200px] mt-0 mb-[-10px]" : "h-[80px] md:h-[140px] w-[180px] md:w-[320px] mt-2 md:mt-4 mb-[-20px] md:mb-[-40px]"}`}
|
className={`relative transition-all duration-300 ${isScrolled ? "h-[50px] md:h-[60px] w-[120px] md:w-[150px]" : "h-[70px] md:h-[100px] w-[160px] md:w-[240px]"}`}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/assets/logo.png"
|
src="/assets/logo.png"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ services:
|
|||||||
- "traefik.http.routers.${PROJECT_NAME}.tls=true"
|
- "traefik.http.routers.${PROJECT_NAME}.tls=true"
|
||||||
- "traefik.http.services.${PROJECT_NAME}.loadbalancer.server.port=3000"
|
- "traefik.http.services.${PROJECT_NAME}.loadbalancer.server.port=3000"
|
||||||
- "traefik.http.routers.${PROJECT_NAME}.middlewares=${PROJECT_NAME}-auth"
|
- "traefik.http.routers.${PROJECT_NAME}.middlewares=${PROJECT_NAME}-auth"
|
||||||
|
- "traefik.docker.network=infra"
|
||||||
|
|
||||||
# Gatekeeper Router (Shared Host + dedicated Subdomain)
|
# Gatekeeper Router (Shared Host + dedicated Subdomain)
|
||||||
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.rule=(Host(`${TRAEFIK_HOST:-mb-grid-solutions.localhost}`) && PathPrefix(`/gatekeeper`)) || Host(`gatekeeper.${TRAEFIK_HOST:-mb-grid-solutions.localhost}`)"
|
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.rule=(Host(`${TRAEFIK_HOST:-mb-grid-solutions.localhost}`) && PathPrefix(`/gatekeeper`)) || Host(`gatekeeper.${TRAEFIK_HOST:-mb-grid-solutions.localhost}`)"
|
||||||
@@ -22,13 +23,14 @@ services:
|
|||||||
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.tls=true"
|
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.tls=true"
|
||||||
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.service=${PROJECT_NAME}-gatekeeper"
|
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.service=${PROJECT_NAME}-gatekeeper"
|
||||||
|
|
||||||
# Auth Middleware Definition
|
|
||||||
- "traefik.http.middlewares.${PROJECT_NAME}-auth.forwardauth.address=http://${PROJECT_NAME}-gatekeeper:3000/api/verify"
|
- "traefik.http.middlewares.${PROJECT_NAME}-auth.forwardauth.address=http://${PROJECT_NAME}-gatekeeper:3000/api/verify"
|
||||||
- "traefik.http.middlewares.${PROJECT_NAME}-auth.forwardauth.trustForwardHeader=true"
|
- "traefik.http.middlewares.${PROJECT_NAME}-auth.forwardauth.trustForwardHeader=true"
|
||||||
- "traefik.http.middlewares.${PROJECT_NAME}-auth.forwardauth.authResponseHeaders=X-Auth-User"
|
- "traefik.http.middlewares.${PROJECT_NAME}-auth.forwardauth.authResponseHeaders=X-Auth-User"
|
||||||
|
- "traefik.docker.network=infra"
|
||||||
|
|
||||||
gatekeeper:
|
gatekeeper:
|
||||||
image: registry.infra.mintel.me/mintel/gatekeeper:latest
|
image: registry.infra.mintel.me/mintel/gatekeeper:latest
|
||||||
|
container_name: ${PROJECT_NAME:-mb-grid-solutions}-gatekeeper
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
infra:
|
infra:
|
||||||
@@ -41,11 +43,14 @@ services:
|
|||||||
PROJECT_NAME: ${PROJECT_NAME:-MB Grid Solutions}
|
PROJECT_NAME: ${PROJECT_NAME:-MB Grid Solutions}
|
||||||
PROJECT_COLOR: ${PROJECT_COLOR:-#82ed20}
|
PROJECT_COLOR: ${PROJECT_COLOR:-#82ed20}
|
||||||
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-.mb-grid-solutions.com}
|
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-.mb-grid-solutions.com}
|
||||||
|
AUTH_COOKIE_NAME: ${AUTH_COOKIE_NAME:-mintel_gatekeeper_session}
|
||||||
|
GATEKEEPER_PASSWORD: ${GATEKEEPER_PASSWORD:-mintel}
|
||||||
# Dedicated Base URL for Gatekeeper subdomain to prevent redirect loops
|
# Dedicated Base URL for Gatekeeper subdomain to prevent redirect loops
|
||||||
NEXT_PUBLIC_BASE_URL: https://gatekeeper.${TRAEFIK_HOST:-mb-grid-solutions.localhost}
|
NEXT_PUBLIC_BASE_URL: https://gatekeeper.${TRAEFIK_HOST:-mb-grid-solutions.localhost}
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.services.${PROJECT_NAME}-gatekeeper.loadbalancer.server.port=3000"
|
- "traefik.http.services.${PROJECT_NAME}-gatekeeper.loadbalancer.server.port=3000"
|
||||||
|
- "traefik.docker.network=infra"
|
||||||
|
|
||||||
directus:
|
directus:
|
||||||
image: directus/directus:11
|
image: directus/directus:11
|
||||||
@@ -68,6 +73,10 @@ services:
|
|||||||
DB_DATABASE: ${DIRECTUS_DB_NAME:-directus}
|
DB_DATABASE: ${DIRECTUS_DB_NAME:-directus}
|
||||||
DB_USER: ${DIRECTUS_DB_USER:-directus}
|
DB_USER: ${DIRECTUS_DB_USER:-directus}
|
||||||
DB_PASSWORD: ${DIRECTUS_DB_PASSWORD:-directus}
|
DB_PASSWORD: ${DIRECTUS_DB_PASSWORD:-directus}
|
||||||
|
# Telemetry & Performance
|
||||||
|
LOGGER_LEVEL: ${LOG_LEVEL:-info}
|
||||||
|
SENTRY_DSN: ${SENTRY_DSN}
|
||||||
|
SENTRY_ENVIRONMENT: ${TARGET:-development}
|
||||||
volumes:
|
volumes:
|
||||||
- ./directus/uploads:/directus/uploads
|
- ./directus/uploads:/directus/uploads
|
||||||
- ./directus/extensions:/directus/extensions
|
- ./directus/extensions:/directus/extensions
|
||||||
@@ -77,8 +86,10 @@ services:
|
|||||||
- "traefik.http.routers.${PROJECT_NAME}-directus.entrypoints=websecure"
|
- "traefik.http.routers.${PROJECT_NAME}-directus.entrypoints=websecure"
|
||||||
- "traefik.http.routers.${PROJECT_NAME}-directus.tls.certresolver=le"
|
- "traefik.http.routers.${PROJECT_NAME}-directus.tls.certresolver=le"
|
||||||
- "traefik.http.routers.${PROJECT_NAME}-directus.tls=true"
|
- "traefik.http.routers.${PROJECT_NAME}-directus.tls=true"
|
||||||
- "traefik.http.routers.${PROJECT_NAME}-directus.middlewares=${PROJECT_NAME}-auth"
|
- "traefik.http.routers.${PROJECT_NAME}-directus.middlewares=${PROJECT_NAME}-forward,compress"
|
||||||
- "traefik.http.services.${PROJECT_NAME}-directus.loadbalancer.server.port=8055"
|
- "traefik.http.services.${PROJECT_NAME}-directus.loadbalancer.server.port=8055"
|
||||||
|
- "traefik.http.middlewares.${PROJECT_NAME}-forward.headers.customrequestheaders.X-Forwarded-Proto=https"
|
||||||
|
- "traefik.docker.network=infra"
|
||||||
|
|
||||||
directus-db:
|
directus-db:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
|
|||||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/types/routes.d.ts";
|
import "./.next/dev/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
14
package.json
14
package.json
@@ -11,13 +11,13 @@
|
|||||||
"lint": "eslint app components lib scripts",
|
"lint": "eslint app components lib scripts",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"directus:bootstrap": "DIRECTUS_URL=http://localhost:8055 npx tsx --env-file=.env scripts/setup-directus.ts",
|
"cms:bootstrap": "DIRECTUS_URL=http://localhost:8055 npx tsx --env-file=.env scripts/setup-directus.ts",
|
||||||
"directus:push:staging": "./scripts/sync-directus.sh push staging",
|
"cms:push:staging": "./scripts/sync-directus.sh push staging",
|
||||||
"directus:pull:staging": "./scripts/sync-directus.sh pull staging",
|
"cms:pull:staging": "./scripts/sync-directus.sh pull staging",
|
||||||
"directus:push:testing": "./scripts/sync-directus.sh push testing",
|
"cms:push:testing": "./scripts/sync-directus.sh push testing",
|
||||||
"directus:pull:testing": "./scripts/sync-directus.sh pull testing",
|
"cms:pull:testing": "./scripts/sync-directus.sh pull testing",
|
||||||
"directus:push:prod": "./scripts/sync-directus.sh push production",
|
"cms:push:prod": "./scripts/sync-directus.sh push production",
|
||||||
"directus:pull:prod": "./scripts/sync-directus.sh pull production",
|
"cms:pull:prod": "./scripts/sync-directus.sh pull production",
|
||||||
"pagespeed:test": "mintel pagespeed test"
|
"pagespeed:test": "mintel pagespeed test"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
|
|||||||
@@ -7,26 +7,36 @@ import { createCollection, createField, updateSettings } from "@directus/sdk";
|
|||||||
const client = createMintelDirectusClient();
|
const client = createMintelDirectusClient();
|
||||||
|
|
||||||
async function setupBranding() {
|
async function setupBranding() {
|
||||||
const prjName = process.env.PROJECT_NAME || "Mintel Project";
|
const prjName = process.env.PROJECT_NAME || "MB Grid Solutions";
|
||||||
const prjColor = process.env.PROJECT_COLOR || "#82ed20";
|
const prjColor = process.env.PROJECT_COLOR || "#82ed20";
|
||||||
|
|
||||||
console.log(`🎨 Setup Directus Branding for ${prjName}...`);
|
console.log(`🎨 Refining Directus Branding for ${prjName}...`);
|
||||||
await ensureDirectusAuthenticated(client);
|
await ensureDirectusAuthenticated(client);
|
||||||
|
|
||||||
const cssInjection = `
|
const cssInjection = `
|
||||||
<style>
|
<style>
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap');
|
||||||
body, .v-app { font-family: 'Inter', sans-serif !important; }
|
|
||||||
|
body, .v-app { font-family: 'Outfit', sans-serif !important; }
|
||||||
|
|
||||||
.public-view .v-card {
|
.public-view .v-card {
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
|
background: rgba(255, 255, 255, 0.9) !important;
|
||||||
border-radius: 32px !important;
|
border-radius: 32px !important;
|
||||||
box-shadow: 0 50px 100px -20px rgba(0, 0, 0, 0.4) !important;
|
box-shadow: 0 50px 100px -20px rgba(0, 0, 0, 0.4) !important;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-navigation-drawer { background: #000c24 !important; }
|
.v-navigation-drawer { background: #000c24 !important; }
|
||||||
|
|
||||||
|
.v-list-item--active {
|
||||||
|
color: ${prjColor} !important;
|
||||||
|
background: rgba(130, 237, 32, 0.1) !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div style="font-family: 'Inter', sans-serif; text-align: center; margin-top: 24px;">
|
<div style="font-family: 'Outfit', sans-serif; text-align: center; margin-top: 24px;">
|
||||||
<p style="color: rgba(255,255,255,0.7); font-size: 14px; margin-bottom: 4px; font-weight: 500;">MINTEL INFRASTRUCTURE ENGINE</p>
|
<p style="color: rgba(255,255,255,0.6); font-size: 11px; letter-spacing: 2px; margin-bottom: 4px; font-weight: 600; text-transform: uppercase;">Mintel Infrastructure Engine</p>
|
||||||
<h1 style="color: #ffffff; font-size: 18px; font-weight: 700; margin: 0;">${prjName.toUpperCase()} <span style="color: ${prjColor};">RELIABILITY.</span></h1>
|
<h1 style="color: #ffffff; font-size: 20px; font-weight: 700; margin: 0; letter-spacing: -0.5px;">${prjName.toUpperCase()} <span style="color: ${prjColor};">SYNC.</span></h1>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -36,25 +46,23 @@ async function setupBranding() {
|
|||||||
project_name: prjName,
|
project_name: prjName,
|
||||||
project_color: prjColor,
|
project_color: prjColor,
|
||||||
public_note: cssInjection,
|
public_note: cssInjection,
|
||||||
|
module_bar_background: "#00081a",
|
||||||
theme_light_overrides: {
|
theme_light_overrides: {
|
||||||
primary: prjColor,
|
primary: prjColor,
|
||||||
borderRadius: "16px",
|
borderRadius: "12px",
|
||||||
navigationBackground: "#000c24",
|
navigationBackground: "#000c24",
|
||||||
navigationForeground: "#ffffff",
|
navigationForeground: "#ffffff",
|
||||||
|
moduleBarBackground: "#00081a",
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} as any),
|
} as any),
|
||||||
);
|
);
|
||||||
console.log("✨ Branding applied!");
|
console.log("✨ Branding applied!");
|
||||||
|
|
||||||
try {
|
await createCollectionAndFields();
|
||||||
await createCollectionAndFields();
|
console.log("🏗️ Schema alignment complete!");
|
||||||
console.log("🏗️ Schema alignment complete!");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ Error aligning schema:", error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Error setting up branding:", error);
|
console.error("❌ Error during bootstrap:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +77,9 @@ async function createCollectionAndFields() {
|
|||||||
meta: {
|
meta: {
|
||||||
icon: "contact_mail",
|
icon: "contact_mail",
|
||||||
display_template: "{{name}} <{{email}}>",
|
display_template: "{{name}} <{{email}}>",
|
||||||
|
group: null,
|
||||||
|
sort: null,
|
||||||
|
collapse: "open",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -84,9 +95,7 @@ async function createCollectionAndFields() {
|
|||||||
);
|
);
|
||||||
console.log(`✅ Collection ${collectionName} created.`);
|
console.log(`✅ Collection ${collectionName} created.`);
|
||||||
} catch {
|
} catch {
|
||||||
console.log(
|
console.log(`ℹ️ Collection ${collectionName} exists.`);
|
||||||
`ℹ️ Collection ${collectionName} already exists or error occured.`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const safeAddField = async (
|
const safeAddField = async (
|
||||||
@@ -102,14 +111,40 @@ async function createCollectionAndFields() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await safeAddField("name", "string", { interface: "input" });
|
await safeAddField("name", "string", {
|
||||||
await safeAddField("email", "string", { interface: "input" });
|
interface: "input",
|
||||||
await safeAddField("company", "string", { interface: "input" });
|
display: "raw",
|
||||||
await safeAddField("message", "text", { interface: "textarea" });
|
width: "half",
|
||||||
|
});
|
||||||
|
await safeAddField("email", "string", {
|
||||||
|
interface: "input",
|
||||||
|
display: "raw",
|
||||||
|
width: "half",
|
||||||
|
});
|
||||||
|
await safeAddField("company", "string", {
|
||||||
|
interface: "input",
|
||||||
|
display: "raw",
|
||||||
|
width: "half",
|
||||||
|
});
|
||||||
|
await safeAddField("message", "text", {
|
||||||
|
interface: "textarea",
|
||||||
|
display: "raw",
|
||||||
|
width: "full",
|
||||||
|
});
|
||||||
await safeAddField("date_created", "timestamp", {
|
await safeAddField("date_created", "timestamp", {
|
||||||
interface: "datetime",
|
interface: "datetime",
|
||||||
special: ["date-created"],
|
special: ["date-created"],
|
||||||
|
display: "datetime",
|
||||||
|
display_options: { relative: true },
|
||||||
|
width: "half",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setupBranding();
|
setupBranding()
|
||||||
|
.then(() => {
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("🚨 Fatal bootstrap error:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Mintel Directus Sync Engine
|
# Configuration
|
||||||
# Synchronizes Directus Data (Postgres + Uploads) between Local and Remote
|
|
||||||
|
|
||||||
REMOTE_HOST="${SSH_HOST:-root@alpha.mintel.me}"
|
REMOTE_HOST="${SSH_HOST:-root@alpha.mintel.me}"
|
||||||
ACTION=$1
|
ACTION=$1
|
||||||
ENV=$2
|
ENV=$2
|
||||||
|
|
||||||
# Help
|
# Help
|
||||||
if [ -z "$ACTION" ] || [ -z "$ENV" ]; then
|
if [ -z "$ACTION" ] || [ -z "$ENV" ]; then
|
||||||
echo "Usage: mintel-sync [push|pull] [testing|staging|production]"
|
echo "Usage: ./scripts/sync-directus.sh [push|pull] [testing|staging|production]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Commands:"
|
echo "Commands:"
|
||||||
echo " push Sync LOCAL data -> REMOTE"
|
echo " push Sync LOCAL data -> REMOTE"
|
||||||
@@ -20,7 +18,10 @@ if [ -z "$ACTION" ] || [ -z "$ENV" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PRJ_ID=$(jq -r .name package.json | sed 's/@mintel\///')
|
# Project Configuration (extracted from package.json and aligned with deploy.yml)
|
||||||
|
PRJ_ID=$(jq -r .name package.json | sed 's/@mintel\///' | sed 's/\.com$//')
|
||||||
|
REMOTE_DIR="/home/deploy/sites/${PRJ_ID}.com"
|
||||||
|
|
||||||
case $ENV in
|
case $ENV in
|
||||||
testing) PROJECT_NAME="${PRJ_ID}-testing"; ENV_FILE=".env.testing" ;;
|
testing) PROJECT_NAME="${PRJ_ID}-testing"; ENV_FILE=".env.testing" ;;
|
||||||
staging) PROJECT_NAME="${PRJ_ID}-staging"; ENV_FILE=".env.staging" ;;
|
staging) PROJECT_NAME="${PRJ_ID}-staging"; ENV_FILE=".env.staging" ;;
|
||||||
@@ -28,41 +29,90 @@ case $ENV in
|
|||||||
*) echo "❌ Invalid environment: $ENV"; exit 1 ;;
|
*) echo "❌ Invalid environment: $ENV"; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
REMOTE_DIR="/home/deploy/sites/${PRJ_ID}.com"
|
# DB Details (matching docker-compose defaults)
|
||||||
|
|
||||||
# DB Details
|
|
||||||
DB_USER="directus"
|
DB_USER="directus"
|
||||||
DB_NAME="directus"
|
DB_NAME="directus"
|
||||||
|
|
||||||
echo "🔍 Detecting local database..."
|
echo "🔍 Detecting local database..."
|
||||||
LOCAL_DB_CONTAINER=$(docker compose ps -q directus-db)
|
LOCAL_DB_CONTAINER=$(docker compose ps -q directus-db)
|
||||||
if [ -z "$LOCAL_DB_CONTAINER" ]; then
|
if [ -z "$LOCAL_DB_CONTAINER" ]; then
|
||||||
echo "❌ Local directus-db container not found. Running?"
|
echo "❌ Local directus-db container not found. Is it running? (npm run dev)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ACTION" == "push" ]; then
|
if [ "$ACTION" == "push" ]; then
|
||||||
echo "🚀 Pushing LOCAL -> $ENV ($PROJECT_NAME)..."
|
echo "🚀 Pushing LOCAL -> $ENV ($PROJECT_NAME)..."
|
||||||
|
|
||||||
|
# 1. DB Dump
|
||||||
|
echo "📦 Dumping local database..."
|
||||||
docker exec "$LOCAL_DB_CONTAINER" pg_dump -U "$DB_USER" --clean --if-exists --no-owner --no-privileges "$DB_NAME" > dump.sql
|
docker exec "$LOCAL_DB_CONTAINER" pg_dump -U "$DB_USER" --clean --if-exists --no-owner --no-privileges "$DB_NAME" > dump.sql
|
||||||
|
|
||||||
|
# 2. Upload Dump
|
||||||
|
echo "📤 Uploading dump to remote server..."
|
||||||
scp dump.sql "$REMOTE_HOST:$REMOTE_DIR/dump.sql"
|
scp dump.sql "$REMOTE_HOST:$REMOTE_DIR/dump.sql"
|
||||||
|
|
||||||
|
# 3. Restore on Remote
|
||||||
|
echo "🔄 Restoring dump on $ENV..."
|
||||||
REMOTE_DB_CONTAINER=$(ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME ps -q directus-db")
|
REMOTE_DB_CONTAINER=$(ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME ps -q directus-db")
|
||||||
|
|
||||||
|
if [ -z "$REMOTE_DB_CONTAINER" ]; then
|
||||||
|
echo "❌ Remote $ENV-db container not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🧹 Wiping remote database schema..."
|
||||||
|
ssh "$REMOTE_HOST" "docker exec $REMOTE_DB_CONTAINER psql -U $DB_USER $DB_NAME -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;'"
|
||||||
|
|
||||||
|
echo "⚡ Restoring database..."
|
||||||
ssh "$REMOTE_HOST" "docker exec -i $REMOTE_DB_CONTAINER psql -U $DB_USER $DB_NAME < $REMOTE_DIR/dump.sql"
|
ssh "$REMOTE_HOST" "docker exec -i $REMOTE_DB_CONTAINER psql -U $DB_USER $DB_NAME < $REMOTE_DIR/dump.sql"
|
||||||
|
|
||||||
|
# 4. Sync Uploads
|
||||||
|
echo "📁 Syncing uploads (Local -> $ENV)..."
|
||||||
rsync -avz --progress ./directus/uploads/ "$REMOTE_HOST:$REMOTE_DIR/directus/uploads/"
|
rsync -avz --progress ./directus/uploads/ "$REMOTE_HOST:$REMOTE_DIR/directus/uploads/"
|
||||||
|
|
||||||
|
# Clean up
|
||||||
rm dump.sql
|
rm dump.sql
|
||||||
ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql"
|
ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql"
|
||||||
echo "✨ Push complete!"
|
|
||||||
|
# 5. Restart Directus to trigger migrations and refresh schema cache
|
||||||
|
echo "🔄 Restarting remote Directus to apply migrations..."
|
||||||
|
ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME restart directus"
|
||||||
|
|
||||||
|
echo "✨ Push to $ENV complete!"
|
||||||
|
|
||||||
elif [ "$ACTION" == "pull" ]; then
|
elif [ "$ACTION" == "pull" ]; then
|
||||||
echo "📥 Pulling $ENV -> LOCAL..."
|
echo "📥 Pulling $ENV Data -> LOCAL..."
|
||||||
|
|
||||||
|
# 1. DB Dump on Remote
|
||||||
|
echo "📦 Dumping remote database ($ENV)..."
|
||||||
REMOTE_DB_CONTAINER=$(ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME ps -q directus-db")
|
REMOTE_DB_CONTAINER=$(ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME ps -q directus-db")
|
||||||
|
|
||||||
|
if [ -z "$REMOTE_DB_CONTAINER" ]; then
|
||||||
|
echo "❌ Remote $ENV-db container not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
ssh "$REMOTE_HOST" "docker exec $REMOTE_DB_CONTAINER pg_dump -U $DB_USER --clean --if-exists --no-owner --no-privileges $DB_NAME > $REMOTE_DIR/dump.sql"
|
ssh "$REMOTE_HOST" "docker exec $REMOTE_DB_CONTAINER pg_dump -U $DB_USER --clean --if-exists --no-owner --no-privileges $DB_NAME > $REMOTE_DIR/dump.sql"
|
||||||
|
|
||||||
|
# 2. Download Dump
|
||||||
|
echo "📥 Downloading dump..."
|
||||||
scp "$REMOTE_HOST:$REMOTE_DIR/dump.sql" dump.sql
|
scp "$REMOTE_HOST:$REMOTE_DIR/dump.sql" dump.sql
|
||||||
|
|
||||||
|
# 3. Restore Locally
|
||||||
|
echo "🧹 Wiping local database schema..."
|
||||||
|
docker exec "$LOCAL_DB_CONTAINER" psql -U "$DB_USER" "$DB_NAME" -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;'
|
||||||
|
|
||||||
|
echo "⚡ Restoring database locally..."
|
||||||
docker exec -i "$LOCAL_DB_CONTAINER" psql -U "$DB_USER" "$DB_NAME" < dump.sql
|
docker exec -i "$LOCAL_DB_CONTAINER" psql -U "$DB_USER" "$DB_NAME" < dump.sql
|
||||||
|
|
||||||
|
# 4. Sync Uploads
|
||||||
|
echo "📁 Syncing uploads ($ENV -> Local)..."
|
||||||
rsync -avz --progress "$REMOTE_HOST:$REMOTE_DIR/directus/uploads/" ./directus/uploads/
|
rsync -avz --progress "$REMOTE_HOST:$REMOTE_DIR/directus/uploads/" ./directus/uploads/
|
||||||
|
|
||||||
|
# Clean up
|
||||||
rm dump.sql
|
rm dump.sql
|
||||||
ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql"
|
ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql"
|
||||||
echo "✨ Pull complete!"
|
|
||||||
|
echo "✨ Pull to Local complete!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user