Files
cms.uldp.edu.vn/models/home.js
2026-04-11 04:46:33 +07:00

357 lines
11 KiB
JavaScript

const mongoose = require("mongoose");
const { Schema } = mongoose;
// Reusable small schemas
const LinkSchema = new Schema(
{
label: { type: String, default: "", maxlength: 32 },
href: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const FloatingContactBrandSchema = new Schema(
{
imageSrc: { type: String, default: "", maxlength: 255 },
imageAlt: { type: String, default: "", maxlength: 60 },
},
{ _id: false },
);
const FloatingContactTriggerSchema = new Schema(
{
imageSrc: { type: String, default: "", maxlength: 255 },
icon: { type: String, default: "fa-comments", maxlength: 64 },
},
{ _id: false },
);
const FloatingContactActionSchema = new Schema(
{
id: { type: String, default: "" },
platform: { type: String, default: "" },
enabled: { type: Boolean, default: true },
label: { type: String, default: "", maxlength: 48 },
subtitle: { type: String, default: "", maxlength: 48 },
href: { type: String, default: "", maxlength: 255 },
iconImage: { type: String, default: "", maxlength: 255 },
iconType: { type: String, default: "iconClass" },
iconClass: { type: String, default: "", maxlength: 120 },
iconText: { type: String, default: "", maxlength: 12 },
order: { type: Number, default: 0 },
},
{ _id: false },
);
const FloatingContactSchema = new Schema(
{
enabled: { type: Boolean, default: true },
position: { type: String, default: "bottom-right" },
panelTitle: { type: String, default: "", maxlength: 72 },
brand: { type: FloatingContactBrandSchema, default: () => ({}) },
trigger: { type: FloatingContactTriggerSchema, default: () => ({}) },
actions: { type: [FloatingContactActionSchema], default: [] },
},
{ _id: false },
);
// Hero slide (for multiple hero items in slider)
const HeroSlideSchema = new Schema(
{
title: { type: String, default: "", maxlength: 72 },
subtitle: { type: String, default: "", maxlength: 48 },
description: { type: String, default: "", maxlength: 220 },
primaryButton: { type: LinkSchema, default: () => ({}) },
secondaryButton: { type: LinkSchema, default: () => ({}) },
heroImage: { type: String, default: "", maxlength: 255 },
videoUrl: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const HeroSchema = new Schema(
{
// Toggle visibility on frontend
enabled: { type: Boolean, default: true },
// Background for whole hero section
backgroundImage: { type: String, default: "", maxlength: 255 },
// Multiple slides
slides: { type: [HeroSlideSchema], default: [] },
// Legacy single-slide fields (backward compatible)
title: { type: String, default: "", maxlength: 72 },
subtitle: { type: String, default: "", maxlength: 48 },
description: { type: String, default: "", maxlength: 220 },
primaryButton: { type: LinkSchema, default: () => ({}) },
secondaryButton: { type: LinkSchema, default: () => ({}) },
heroImage: { type: String, default: "", maxlength: 255 },
videoUrl: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const WhyChooseUsItemSchema = new Schema(
{
icon: { type: String, default: "", maxlength: 255 },
title: { type: String, default: "", maxlength: 40 },
description: { type: String, default: "", maxlength: 72 },
},
{ _id: false },
);
const WhyChooseUsSchema = new Schema(
{
// Toggle visibility on frontend
enabled: { type: Boolean, default: true },
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
description: { type: String, default: "" },
highlightWord: { type: String, default: "" },
mainImage: { type: String, default: "" },
secondaryImage: { type: String, default: "" },
items: { type: [WhyChooseUsItemSchema], default: [] },
features: { type: [{ type: String, maxlength: 96 }], default: [] },
ctaButton: { type: LinkSchema, default: () => ({}) },
},
{ _id: false },
);
const VisaSolutionItemSchema = new Schema(
{
number: { type: String, default: "", maxlength: 4 },
title: { type: String, default: "", maxlength: 56 },
description: { type: String, default: "", maxlength: 180 },
link: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const VisaSolutionsSchema = new Schema(
{
// Toggle visibility on frontend
enabled: { type: Boolean, default: true },
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
items: { type: [VisaSolutionItemSchema], default: [] },
},
{ _id: false },
);
const VisaCountrySchema = new Schema(
{
name: { type: String, default: "", maxlength: 40 },
code: { type: String, default: "", maxlength: 12 },
flag: { type: String, default: "", maxlength: 255 },
link: { type: String, default: "", maxlength: 255 },
visaTypes: { type: [{ type: String, maxlength: 48 }], default: [] },
},
{ _id: false },
);
const VisaCountriesSchema = new Schema(
{
// Toggle visibility on frontend
enabled: { type: Boolean, default: true },
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
description: { type: String, default: "" },
countries: { type: [VisaCountrySchema], default: [] },
ctaButton: { type: LinkSchema, default: () => ({}) },
},
{ _id: false },
);
const TestimonialSchema = new Schema(
{
name: { type: String, default: "", maxlength: 48 },
role: { type: String, default: "", maxlength: 48 },
country: { type: String, default: "", maxlength: 48 },
rating: { type: Number, default: 5 },
comment: { type: String, default: "", maxlength: 280 },
avatar: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const TestimonialsSchema = new Schema(
{
// Toggle visibility on frontend
enabled: { type: Boolean, default: true },
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
videoUrl: { type: String, default: "" },
videoThumbnail: { type: String, default: "" },
items: { type: [TestimonialSchema], default: [] },
},
{ _id: false },
);
const VideoGallerySchema = new Schema(
{
// Toggle visibility on frontend
enabled: { type: Boolean, default: true },
heading: { type: String, default: "" },
videoUrl: { type: String, default: "" },
thumbnail: { type: String, default: "" },
},
{ _id: false },
);
const FaqItemSchema = new Schema(
{
question: { type: String, default: "", maxlength: 120 },
answer: { type: String, default: "", maxlength: 320 },
},
{ _id: false },
);
const FaqSchema = new Schema(
{
// Toggle visibility on frontend
enabled: { type: Boolean, default: true },
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
description: { type: String, default: "" },
ctaButton: { type: LinkSchema, default: () => ({}) },
items: { type: [FaqItemSchema], default: [] },
},
{ _id: false },
);
const AchievementItemSchema = new Schema(
{
value: { type: String, default: "", maxlength: 6 },
suffix: { type: String, default: "", maxlength: 4 },
label: { type: String, default: "", maxlength: 40 },
description: { type: String, default: "", maxlength: 120 },
},
{ _id: false },
);
const AchievementsSchema = new Schema(
{
// Toggle visibility on frontend
enabled: { type: Boolean, default: true },
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
items: { type: [AchievementItemSchema], default: [] },
},
{ _id: false },
);
const VisaConsultancyItemSchema = new Schema(
{
name: { type: String, default: "", maxlength: 48 },
icon: { type: String, default: "", maxlength: 255 },
year: { type: String, default: "", maxlength: 8 },
},
{ _id: false },
);
const VisaConsultancySchema = new Schema(
{
items: { type: [VisaConsultancyItemSchema], default: [] },
},
{ _id: false },
);
const BrandItemSchema = new Schema(
{
logo: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const BrandsSchema = new Schema(
{
items: { type: [BrandItemSchema], default: [] },
},
{ _id: false },
);
const PartnersSchema = new Schema(
{
// Toggle visibility on frontend
enabled: { type: Boolean, default: true },
visaConsultancy: { type: VisaConsultancySchema, default: () => ({}) },
brands: { type: BrandsSchema, default: () => ({}) },
},
{ _id: false },
);
const BlogPreviewItemSchema = new Schema(
{
title: { type: String, default: "", maxlength: 120 },
excerpt: { type: String, default: "", maxlength: 280 },
category: { type: String, default: "", maxlength: 48 },
date: { type: String, default: "" }, // keep as string for easy JSON compatibility (e.g. "2025-08-20")
author: {
name: { type: String, default: "", maxlength: 48 },
avatar: { type: String, default: "" },
},
comments: { type: Number, default: 0 },
link: { type: String, default: "", maxlength: 255 },
thumbnail: { type: String, default: "" },
},
{ _id: false },
);
const BlogPreviewSchema = new Schema(
{
// Toggle visibility on frontend
enabled: { type: Boolean, default: true },
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
ctaButton: { type: LinkSchema, default: () => ({}) },
items: { type: [BlogPreviewItemSchema], default: [] },
selectedBlogIds: [{ type: Schema.Types.ObjectId, ref: 'Blog' }],
},
{ _id: false },
);
/**
* Home page content model
*
* NOTE:
* - This schema is based on `hailearning.edu.vn/app/home.json`.
* - `strict: false` keeps backward compatibility with any existing CMS-only sections
* (e.g. about/missionVision/programs/newsletter/latestPosts...) that the admin UI might still send.
*/
const HomeSchema = new Schema(
{
hero: { type: HeroSchema, default: () => ({}) },
whyChooseUs: { type: WhyChooseUsSchema, default: () => ({}) },
visaSolutions: { type: VisaSolutionsSchema, default: () => ({}) },
visaCountries: { type: VisaCountriesSchema, default: () => ({}) },
testimonials: { type: TestimonialsSchema, default: () => ({}) },
videoGallery: { type: VideoGallerySchema, default: () => ({}) },
faq: { type: FaqSchema, default: () => ({}) },
achievements: { type: AchievementsSchema, default: () => ({}) },
partners: { type: PartnersSchema, default: () => ({}) },
blogPreview: { type: BlogPreviewSchema, default: () => ({}) },
floatingContact: { type: FloatingContactSchema, default: () => ({}) },
},
{
timestamps: true,
strict: false,
},
);
// Đảm bảo chỉ có 1 document duy nhất (singleton pattern)
HomeSchema.index({ createdAt: 1 }, { unique: false });
module.exports = mongoose.model("Home", HomeSchema);