feat: Integrate Directus CMS, add i18n with next-intl, and configure project tooling with pnpm, husky, and commitlint.**
This commit is contained in:
108
scripts/setup-directus.ts
Normal file
108
scripts/setup-directus.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import {
|
||||
createMintelDirectusClient,
|
||||
ensureDirectusAuthenticated,
|
||||
} from "@mintel/next-utils";
|
||||
import { createCollection, createField, updateSettings } from "@directus/sdk";
|
||||
|
||||
const client = createMintelDirectusClient();
|
||||
|
||||
async function setupBranding() {
|
||||
const prjName = process.env.PROJECT_NAME || "Mintel Project";
|
||||
const prjColor = process.env.PROJECT_COLOR || "#82ed20";
|
||||
|
||||
console.log(`🎨 Setup Directus Branding for ${prjName}...`);
|
||||
await ensureDirectusAuthenticated(client);
|
||||
|
||||
const cssInjection = `
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
body, .v-app { font-family: 'Inter', sans-serif !important; }
|
||||
.public-view .v-card {
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 32px !important;
|
||||
box-shadow: 0 50px 100px -20px rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
.v-navigation-drawer { background: #000c24 !important; }
|
||||
</style>
|
||||
<div style="font-family: 'Inter', 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>
|
||||
<h1 style="color: #ffffff; font-size: 18px; font-weight: 700; margin: 0;">${prjName.toUpperCase()} <span style="color: ${prjColor};">RELIABILITY.</span></h1>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
await client.request(
|
||||
updateSettings({
|
||||
project_name: prjName,
|
||||
project_color: prjColor,
|
||||
public_note: cssInjection,
|
||||
theme_light_overrides: {
|
||||
primary: prjColor,
|
||||
borderRadius: "16px",
|
||||
navigationBackground: "#000c24",
|
||||
navigationForeground: "#ffffff",
|
||||
},
|
||||
} as any),
|
||||
);
|
||||
console.log("✨ Branding applied!");
|
||||
|
||||
try {
|
||||
await createCollectionAndFields();
|
||||
console.log("🏗️ Schema alignment complete!");
|
||||
} catch (error) {
|
||||
console.error("❌ Error aligning schema:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Error setting up branding:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function createCollectionAndFields() {
|
||||
const collectionName = "contact_submissions";
|
||||
|
||||
try {
|
||||
await client.request(
|
||||
createCollection({
|
||||
collection: collectionName,
|
||||
schema: {},
|
||||
meta: {
|
||||
icon: "contact_mail",
|
||||
display_template: "{{name}} <{{email}}>",
|
||||
},
|
||||
} as any),
|
||||
);
|
||||
|
||||
// Add ID field
|
||||
await client.request(
|
||||
createField(collectionName, {
|
||||
field: "id",
|
||||
type: "integer",
|
||||
meta: { hidden: true },
|
||||
schema: { is_primary_key: true, has_auto_increment: true },
|
||||
}),
|
||||
);
|
||||
console.log(`✅ Collection ${collectionName} created.`);
|
||||
} catch (e: any) {
|
||||
console.log(`ℹ️ Collection ${collectionName} status: ${e.message}`);
|
||||
}
|
||||
|
||||
const safeAddField = async (field: string, type: string, meta: any = {}) => {
|
||||
try {
|
||||
await client.request(createField(collectionName, { field, type, meta }));
|
||||
console.log(`✅ Field ${field} added.`);
|
||||
} catch (e: any) {
|
||||
// Ignore if exists
|
||||
}
|
||||
};
|
||||
|
||||
await safeAddField("name", "string", { interface: "input" });
|
||||
await safeAddField("email", "string", { interface: "input" });
|
||||
await safeAddField("company", "string", { interface: "input" });
|
||||
await safeAddField("message", "text", { interface: "textarea" });
|
||||
await safeAddField("date_created", "timestamp", {
|
||||
interface: "datetime",
|
||||
special: ["date-created"],
|
||||
});
|
||||
}
|
||||
|
||||
setupBranding();
|
||||
68
scripts/sync-directus.sh
Executable file
68
scripts/sync-directus.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Mintel Directus Sync Engine
|
||||
# Synchronizes Directus Data (Postgres + Uploads) between Local and Remote
|
||||
|
||||
REMOTE_HOST="${SSH_HOST:-root@alpha.mintel.me}"
|
||||
ACTION=$1
|
||||
ENV=$2
|
||||
|
||||
# Help
|
||||
if [ -z "$ACTION" ] || [ -z "$ENV" ]; then
|
||||
echo "Usage: mintel-sync [push|pull] [testing|staging|production]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " push Sync LOCAL data -> REMOTE"
|
||||
echo " pull Sync REMOTE data -> LOCAL"
|
||||
echo ""
|
||||
echo "Environments:"
|
||||
echo " testing, staging, production"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PRJ_ID=$(jq -r .name package.json | sed 's/@mintel\///')
|
||||
case $ENV in
|
||||
testing) PROJECT_NAME="${PRJ_ID}-testing"; ENV_FILE=".env.testing" ;;
|
||||
staging) PROJECT_NAME="${PRJ_ID}-staging"; ENV_FILE=".env.staging" ;;
|
||||
production) PROJECT_NAME="${PRJ_ID}-prod"; ENV_FILE=".env.prod" ;;
|
||||
*) echo "❌ Invalid environment: $ENV"; exit 1 ;;
|
||||
esac
|
||||
|
||||
REMOTE_DIR="/home/deploy/sites/${PRJ_ID}.com"
|
||||
|
||||
# DB Details
|
||||
DB_USER="directus"
|
||||
DB_NAME="directus"
|
||||
|
||||
echo "🔍 Detecting local database..."
|
||||
LOCAL_DB_CONTAINER=$(docker compose ps -q directus-db)
|
||||
if [ -z "$LOCAL_DB_CONTAINER" ]; then
|
||||
echo "❌ Local directus-db container not found. Running?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$ACTION" == "push" ]; then
|
||||
echo "🚀 Pushing LOCAL -> $ENV ($PROJECT_NAME)..."
|
||||
docker exec "$LOCAL_DB_CONTAINER" pg_dump -U "$DB_USER" --clean --if-exists --no-owner --no-privileges "$DB_NAME" > dump.sql
|
||||
scp dump.sql "$REMOTE_HOST:$REMOTE_DIR/dump.sql"
|
||||
|
||||
REMOTE_DB_CONTAINER=$(ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME ps -q directus-db")
|
||||
ssh "$REMOTE_HOST" "docker exec -i $REMOTE_DB_CONTAINER psql -U $DB_USER $DB_NAME < $REMOTE_DIR/dump.sql"
|
||||
|
||||
rsync -avz --progress ./directus/uploads/ "$REMOTE_HOST:$REMOTE_DIR/directus/uploads/"
|
||||
rm dump.sql
|
||||
ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql"
|
||||
echo "✨ Push complete!"
|
||||
|
||||
elif [ "$ACTION" == "pull" ]; then
|
||||
echo "📥 Pulling $ENV -> LOCAL..."
|
||||
REMOTE_DB_CONTAINER=$(ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME ps -q directus-db")
|
||||
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"
|
||||
scp "$REMOTE_HOST:$REMOTE_DIR/dump.sql" dump.sql
|
||||
|
||||
docker exec -i "$LOCAL_DB_CONTAINER" psql -U "$DB_USER" "$DB_NAME" < dump.sql
|
||||
rsync -avz --progress "$REMOTE_HOST:$REMOTE_DIR/directus/uploads/" ./directus/uploads/
|
||||
rm dump.sql
|
||||
ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql"
|
||||
echo "✨ Pull complete!"
|
||||
fi
|
||||
8
scripts/validate-env.ts
Normal file
8
scripts/validate-env.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { validateMintelEnv } from "@mintel/next-utils";
|
||||
|
||||
try {
|
||||
validateMintelEnv();
|
||||
console.log("✅ Environment variables validated");
|
||||
} catch (error) {
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user