Files
cms.techvanguard.vn/scripts/2026_04_24_150000_submissions.js
T
Tống Thành Đạt f6f2e0e03b feat(admin): implement unified submission and newsletter management
Introduce a centralized system to manage all website form submissions and newsletter subscriptions.
- Add `Submission` and `NewsletterSubscription` models with MongoDB schema validation
- Implement `submissionController` and `newsletterSubscriptionController` for CRUD operations and filtering
- Create a unified admin UI for reviewing submissions across different sources (home, request, contact, partnership, newsletter)
- Add database migration scripts for creating collections and indexes
- Refactor partnership inquiry forms to use a fixed field structure
- Update admin navigation and server CORS settings to support PATCH requests
2026-04-24 17:11:23 +07:00

125 lines
2.9 KiB
JavaScript

require("dotenv").config();
const mongoose = require("mongoose");
const connectDB = require("../config/database");
const COLLECTION_NAME = "submissions";
const validator = {
$jsonSchema: {
bsonType: "object",
required: ["source", "name", "status", "createdAt", "updatedAt"],
properties: {
source: {
enum: ["home", "request", "contact", "partnership"],
description: "Source must be one of the supported submission sources",
},
pageUrl: {
bsonType: "string",
},
name: {
bsonType: "string",
minLength: 1,
maxLength: 160,
},
email: {
bsonType: "string",
pattern: "^$|^\\S+@\\S+\\.\\S+$",
},
phone: {
bsonType: "string",
pattern: "^$|^[+()\\d\\s.-]{7,20}$",
},
payload: {
bsonType: "object",
},
status: {
enum: ["new", "contacted", "info_provided", "closed"],
},
internalNote: {
bsonType: "string",
maxLength: 2000,
},
ipAddress: {
bsonType: "string",
},
userAgent: {
bsonType: "string",
},
createdAt: {
bsonType: "date",
},
updatedAt: {
bsonType: "date",
},
},
},
};
async function ensureCollection(db) {
const collections = await db
.listCollections({ name: COLLECTION_NAME }, { nameOnly: true })
.toArray();
if (collections.length === 0) {
await db.createCollection(COLLECTION_NAME, {
validator,
validationLevel: "moderate",
validationAction: "error",
});
return;
}
await db.command({
collMod: COLLECTION_NAME,
validator,
validationLevel: "moderate",
validationAction: "error",
});
}
async function ensureIndexes(collection) {
await dropLegacyIndex(collection, "source_1");
await dropLegacyIndex(collection, "status_1");
await collection.createIndex(
{ source: 1, status: 1, createdAt: -1 },
{ name: "source_1_status_1_createdAt_-1" },
);
await collection.createIndex({ email: 1 }, { name: "email_1" });
await collection.createIndex(
{ name: "text", email: "text", phone: "text" },
{ name: "name_text_email_text_phone_text" },
);
}
async function dropLegacyIndex(collection, name) {
try {
await collection.dropIndex(name);
} catch (error) {
if (error.codeName !== "IndexNotFound") {
throw error;
}
}
}
async function migrate() {
try {
await connectDB();
const db = mongoose.connection.db;
await ensureCollection(db);
await ensureIndexes(db.collection(COLLECTION_NAME));
console.log("Submissions collection migration completed successfully");
await mongoose.disconnect();
process.exit(0);
} catch (error) {
console.error("Submissions migration error:", error);
process.exit(1);
}
}
if (require.main === module) {
migrate();
}
module.exports = { migrate };