refactor: overhaul Directus sync script with schema wiping and restart, update branding, and rename CMS scripts.
This commit is contained in:
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,13 +111,32 @@ 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",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,92 @@ 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
|
||||||
|
|
||||||
|
# Wipe remote DB clean before restore to avoid constraint errors
|
||||||
|
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
|
||||||
|
# Wipe local DB clean before restore to avoid constraint errors
|
||||||
|
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