forked from UKSOURCE/cms.lams
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
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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 };
|
||||
@@ -0,0 +1,92 @@
|
||||
require("dotenv").config();
|
||||
const mongoose = require("mongoose");
|
||||
const connectDB = require("../config/database");
|
||||
|
||||
const COLLECTION_NAME = "newsletter_subscriptions";
|
||||
|
||||
const validator = {
|
||||
$jsonSchema: {
|
||||
bsonType: "object",
|
||||
required: ["email", "status", "createdAt", "updatedAt"],
|
||||
properties: {
|
||||
email: {
|
||||
bsonType: "string",
|
||||
pattern: "^\\S+@\\S+\\.\\S+$",
|
||||
},
|
||||
status: {
|
||||
enum: ["subscribed", "unsubscribed"],
|
||||
},
|
||||
pageUrl: {
|
||||
bsonType: "string",
|
||||
},
|
||||
ipAddress: {
|
||||
bsonType: "string",
|
||||
},
|
||||
userAgent: {
|
||||
bsonType: "string",
|
||||
},
|
||||
internalNote: {
|
||||
bsonType: "string",
|
||||
maxLength: 2000,
|
||||
},
|
||||
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 collection.createIndex({ email: 1 }, { name: "email_1", unique: true });
|
||||
await collection.createIndex(
|
||||
{ status: 1, createdAt: -1 },
|
||||
{ name: "status_1_createdAt_-1" },
|
||||
);
|
||||
await collection.createIndex({ createdAt: -1 }, { name: "createdAt_-1" });
|
||||
}
|
||||
|
||||
async function migrate() {
|
||||
try {
|
||||
await connectDB();
|
||||
const db = mongoose.connection.db;
|
||||
await ensureCollection(db);
|
||||
await ensureIndexes(db.collection(COLLECTION_NAME));
|
||||
console.log("Newsletter subscriptions migration completed successfully");
|
||||
await mongoose.disconnect();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("Newsletter subscriptions migration error:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
migrate();
|
||||
}
|
||||
|
||||
module.exports = { migrate };
|
||||
Reference in New Issue
Block a user