From 7ec9bccad58082c85767a970a8ab4114e271a65d Mon Sep 17 00:00:00 2001 From: Wini_Fy Date: Thu, 5 Feb 2026 21:19:51 +0700 Subject: [PATCH 1/7] refactor: enhance home page structure and content management --- controllers/homeController.js | 52 ++- data/home.json | 11 +- models/home.js | 26 +- views/admin/home/index.ejs | 24 +- views/admin/home/sections/faq.ejs | 102 +++-- views/admin/home/sections/hero.ejs | 425 +++++++++++++------- views/admin/home/sections/testimonials.ejs | 268 +++++++----- views/admin/home/sections/videoGallery.ejs | 62 ++- views/admin/home/sections/visaCountries.ejs | 180 ++++----- views/admin/home/sections/visaSolutions.ejs | 163 +++++--- views/admin/home/sections/whyChooseUs.ejs | 240 +++++++---- 11 files changed, 951 insertions(+), 602 deletions(-) diff --git a/controllers/homeController.js b/controllers/homeController.js index d4eac73..fbc55e8 100644 --- a/controllers/homeController.js +++ b/controllers/homeController.js @@ -7,8 +7,28 @@ const getHomeDoc = async () => Home.findOne().sort({ updatedAt: -1 }); const getHomeData = async () => (await getHomeDoc())?.toObject() || {}; const getDefaultHomeData = () => ({ - hero: { title: "", subtitle: "", description: "", backgroundImage: "", videoUrl: "", primaryButton: {}, secondaryButton: {} }, - whyChooseUs: { heading: "", subheading: "", description: "", items: [], features: [], ctaButton: {} }, + hero: { + backgroundImage: "", + slides: [], + title: "", + subtitle: "", + description: "", + heroImage: "", + videoUrl: "", + primaryButton: {}, + secondaryButton: {}, + }, + whyChooseUs: { + heading: "", + subheading: "", + description: "", + highlightWord: "", + mainImage: "", + secondaryImage: "", + items: [], + features: [], + ctaButton: {}, + }, visaSolutions: { heading: "", subheading: "", items: [] }, visaCountries: { heading: "", subheading: "", description: "", countries: [], ctaButton: {} }, testimonials: { heading: "", subheading: "", videoUrl: "", videoThumbnail: "", items: [] }, @@ -16,10 +36,10 @@ const getDefaultHomeData = () => ({ faq: { heading: "", subheading: "", description: "", ctaButton: {}, items: [] }, achievements: { heading: "", subheading: "", items: [] }, partners: { visaConsultancy: { items: [] }, brands: { items: [] } }, - blogPreview: { - heading: "Latest Insights & Updates", - subheading: "Visa Tips & Guides", - ctaButton: { label: "View All Articles", href: "/blog" }, + blogPreview: { + heading: "Latest Insights & Updates", + subheading: "Visa Tips & Guides", + ctaButton: { label: "View All Articles", href: "/blog" }, items: [], selectedBlogIds: [] // Array of manually selected blog IDs }, @@ -30,7 +50,7 @@ exports.index = async (req, res) => { try { let data = await getHomeData(); const defaults = getDefaultHomeData(); - + // Merge dữ liệu mặc định cho tất cả các phần const sections = Object.keys(defaults); sections.forEach(s => { @@ -39,7 +59,7 @@ exports.index = async (req, res) => { const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; const backendUrl = process.env.BACKEND_URL || "http://localhost:3001"; - + // Lấy tất cả blog để chọn trong CMS const allBlogs = await Blog.find({ status: "published" }).sort({ createdAt: -1 }).lean(); @@ -65,8 +85,8 @@ exports.index = async (req, res) => { exports.update = async (req, res) => { try { const sections = [ - "hero", "whyChooseUs", "visaSolutions", "visaCountries", - "testimonials", "videoGallery", "faq", "achievements", + "hero", "whyChooseUs", "visaSolutions", "visaCountries", + "testimonials", "videoGallery", "faq", "achievements", "partners", "blogPreview" ]; @@ -122,20 +142,20 @@ exports.api = async (req, res) => { // === Xử lý Blog Preview động === const blogPreview = data.blogPreview || {}; let blogs = []; - + // Nếu có chọn blog cụ thể if (blogPreview.selectedBlogIds && blogPreview.selectedBlogIds.length > 0) { - blogs = await Blog.find({ + blogs = await Blog.find({ _id: { $in: blogPreview.selectedBlogIds }, - status: "published" + status: "published" }).lean(); - + // Sắp xếp theo thứ tự đã chọn trong selectedBlogIds blogs.sort((a, b) => { return blogPreview.selectedBlogIds.indexOf(a._id.toString()) - blogPreview.selectedBlogIds.indexOf(b._id.toString()); }); - } - + } + // Nếu không chọn hoặc chọn nhưng không đủ, lấy thêm 3 bài mới nhất (hoặc bù vào) if (blogs.length === 0) { blogs = await Blog.find({ status: "published" }) diff --git a/data/home.json b/data/home.json index 92bd9ab..0d4df4a 100644 --- a/data/home.json +++ b/data/home.json @@ -1,7 +1,6 @@ { - "hero": { - "title": "From Application to Visa – We've Got You Covered", + "title": "From Application to Visa – We’ve Got You Covered", "subtitle": "Global Education Simplified", "description": "We guide you through every step of the education visa process, from initial application to final approval, ensuring a smooth, hassle-free journey.", "primaryButton": { @@ -48,25 +47,25 @@ "number": "01", "title": "Student Visa Guidance", "description": "Assistance with admission, documentation, and visa application.Assistance", - "link": "/service-details" + "link": "/services/student-visa" }, { "number": "02", "title": "PTE Exam Preparation", "description": "We provide expert guidance and personalized support throughout the education visa process,", - "link": "/service-details" + "link": "/services/pte-exam" }, { "number": "03", "title": "University Selection Assistance", "description": "We provide expert guidance and personalized support throughout the education visa process,", - "link": "/service-details" + "link": "/services/university-selection" }, { "number": "04", "title": "IELTS Exam Preparation", "description": "We provide expert guidance and personalized support throughout the education visa process,", - "link": "/service-details" + "link": "/services/ielts-exam" } ] }, diff --git a/models/home.js b/models/home.js index 429d3be..09d3d52 100644 --- a/models/home.js +++ b/models/home.js @@ -11,14 +11,35 @@ const LinkSchema = new Schema( { _id: false }, ); -const HeroSchema = new Schema( +// Hero slide (for multiple hero items in slider) +const HeroSlideSchema = new Schema( { title: { type: String, default: "" }, subtitle: { type: String, default: "" }, description: { type: String, default: "" }, primaryButton: { type: LinkSchema, default: () => ({}) }, secondaryButton: { type: LinkSchema, default: () => ({}) }, + heroImage: { type: String, default: "" }, + videoUrl: { type: String, default: "" }, + }, + { _id: false }, +); + +const HeroSchema = new Schema( + { + // Background for whole hero section backgroundImage: { type: String, default: "" }, + + // Multiple slides + slides: { type: [HeroSlideSchema], default: [] }, + + // Legacy single-slide fields (backward compatible) + title: { type: String, default: "" }, + subtitle: { type: String, default: "" }, + description: { type: String, default: "" }, + primaryButton: { type: LinkSchema, default: () => ({}) }, + secondaryButton: { type: LinkSchema, default: () => ({}) }, + heroImage: { type: String, default: "" }, videoUrl: { type: String, default: "" }, }, { _id: false }, @@ -38,6 +59,9 @@ const WhyChooseUsSchema = new Schema( 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: [String], default: [] }, ctaButton: { type: LinkSchema, default: () => ({}) }, diff --git a/views/admin/home/index.ejs b/views/admin/home/index.ejs index 4296913..f094b65 100644 --- a/views/admin/home/index.ejs +++ b/views/admin/home/index.ejs @@ -88,15 +88,15 @@
<%- include('sections/hero') %> - <%- include('sections/whyChooseUs') %> - <%- include('sections/visaSolutions') %> - <%- include('sections/visaCountries') %> - <%- include('sections/testimonials') %> - <%- include('sections/videoGallery') %> - <%- include('sections/faq') %> - <%- include('sections/achievements') %> - <%- include('sections/partners') %> - <%- include('sections/blogPreview') %> + <%- include('sections/whyChooseUs') %> + <%- include('sections/visaSolutions') %> + <%- include('sections/visaCountries') %> + <%- include('sections/testimonials') %> + <%- include('sections/videoGallery') %> + <%- include('sections/faq') %> + <%- include('sections/achievements') %> + <%- include('sections/partners') %> + <%- include('sections/blogPreview') %>
@@ -136,9 +136,9 @@ document.addEventListener("DOMContentLoaded", function () { const form = document.querySelector("form"); if (form) { - form.addEventListener("submit", function(e) { + form.addEventListener("submit", function (e) { console.log("Form submitting, collecting data from scrapers..."); - + // Tự động thu gom dữ liệu từ các section đã đăng ký Object.keys(window.homeScrapers).forEach(section => { const input = document.getElementById(section + 'Json'); @@ -164,7 +164,7 @@ // --- UTILITIES (Dùng chung) --- function initImageUploads() { - document.addEventListener("click", function(e) { + document.addEventListener("click", function (e) { const btn = e.target.closest(".btn-upload-image"); if (btn) { document.getElementById("currentImageType").value = btn.dataset.imageType; diff --git a/views/admin/home/sections/faq.ejs b/views/admin/home/sections/faq.ejs index 021c06a..7176faf 100644 --- a/views/admin/home/sections/faq.ejs +++ b/views/admin/home/sections/faq.ejs @@ -13,32 +13,18 @@
- +
- +
- +
@@ -48,16 +34,33 @@
-
+
FAQ Items
-
- <% (data.faq?.items || []).forEach(function(item, index) { %> -
+
+ <% + const faqItems = (data.faq && Array.isArray(data.faq.items) && data.faq.items.length === 5) + ? data.faq.items + : (data.faq && Array.isArray(data.faq.items) && data.faq.items.length > 0) + ? (function () { + const clone = data.faq.items.slice(0, 5); + while (clone.length < 5) clone.push({}); + return clone; + })() + : [{}, {}, {}, {}, {}]; + %> + <% faqItems.forEach(function(item, index) { %> +
+
+
FAQ + + <%= index + 1 %> + +
+
-
FAQ <%= index + 1 %>
@@ -98,23 +101,13 @@
- +
- +
@@ -122,3 +115,34 @@
+ + \ No newline at end of file diff --git a/views/admin/home/sections/hero.ejs b/views/admin/home/sections/hero.ejs index c1f333f..92a0e84 100644 --- a/views/admin/home/sections/hero.ejs +++ b/views/admin/home/sections/hero.ejs @@ -1,157 +1,312 @@
- +
-
-
+
+
- Basic Information + Hero Background
-
- - -
-
- - -
-
- - -
- -
<% if (data.hero?.backgroundImage) { %> -
- Background preview +
+ Background preview +
+ <% } %> +
+
+
+
+
+ + +
+
+
+
+ Hero Slides +
+ +
+
+ <% const existingSlides=(data.hero && Array.isArray(data.hero.slides) && data.hero.slides.length> 0) + ? data.hero.slides + : [{ + title: data.hero?.title || '', + subtitle: data.hero?.subtitle || '', + description: data.hero?.description || '', + heroImage: data.hero?.heroImage || '', + videoUrl: data.hero?.videoUrl || '', + primaryButton: data.hero?.primaryButton || {}, + secondaryButton: data.hero?.secondaryButton || {}, + }]; + %> + + <% existingSlides.forEach(function(slide, index) { %> +
+
+ Slide + + <%= index + 1 %> + + + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + Recommended size: 893x848px +
+ + +
+ <% if (slide.heroImage) { %> +
+ Hero image preview +
+ <% } %> +
+
+ + +
+ + +
+
+
+ Primary Button +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+ Secondary Button +
+
+
+ + +
+
+ + +
+
+
+
+
+
- <% } %> -
-
- - -
-
-
-
-
- - -
-
-
-
- Primary Button -
-
-
-
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
- Secondary Button -
-
-
-
-
- - -
-
- - -
-
+ <% }); %>
+ + \ No newline at end of file diff --git a/views/admin/home/sections/testimonials.ejs b/views/admin/home/sections/testimonials.ejs index 55f91ac..650af05 100644 --- a/views/admin/home/sections/testimonials.ejs +++ b/views/admin/home/sections/testimonials.ejs @@ -13,49 +13,26 @@
- +
- +
- +
- -
@@ -68,92 +45,167 @@
-
+
Testimonials
+
-
+
<% (data.testimonials?.items || []).forEach(function(item, index) { %> -
-
-
Testimonial <%= index + 1 %>
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
- - +
+
+
Testimonial + <%= index + 1 %> +
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
-
- <% }); %> + <% }); %>
+ + \ No newline at end of file diff --git a/views/admin/home/sections/videoGallery.ejs b/views/admin/home/sections/videoGallery.ejs index 4398814..3ceb355 100644 --- a/views/admin/home/sections/videoGallery.ejs +++ b/views/admin/home/sections/videoGallery.ejs @@ -12,52 +12,30 @@
- +
- +
- -
<% if (data.videoGallery?.thumbnail) { %> -
- Thumbnail preview -
- <% } %> +
+ Thumbnail preview +
+ <% } %>
@@ -65,3 +43,17 @@
+ + \ No newline at end of file diff --git a/views/admin/home/sections/visaCountries.ejs b/views/admin/home/sections/visaCountries.ejs index 73472f8..67012ad 100644 --- a/views/admin/home/sections/visaCountries.ejs +++ b/views/admin/home/sections/visaCountries.ejs @@ -13,114 +13,76 @@
- +
- +
- +
- +
- Countries + Featured Country
+ This country is used in the home page feature section.
- <% (data.visaCountries?.countries || []).forEach(function(country, index) { %> -
-
-
Country <%= index + 1 %>
-
-
- - -
-
- - -
-
- -
- - + <% const featured=(data.visaCountries && Array.isArray(data.visaCountries.countries) && + data.visaCountries.countries.length> 0) + ? data.visaCountries.countries[0] + : {}; + %> +
+
+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+
+ +
-
-
- - -
-
- -
-
- <% }); %>
@@ -137,23 +99,13 @@
- +
- +
@@ -161,3 +113,33 @@
+ + \ No newline at end of file diff --git a/views/admin/home/sections/visaSolutions.ejs b/views/admin/home/sections/visaSolutions.ejs index bfad6f1..1fefdcb 100644 --- a/views/admin/home/sections/visaSolutions.ejs +++ b/views/admin/home/sections/visaSolutions.ejs @@ -13,23 +13,13 @@
- +
- +
@@ -39,62 +29,109 @@
-
+
Visa Solutions Items
-
- <% (data.visaSolutions?.items || []).forEach(function(item, index) { %> -
-
-
Service <%= index + 1 %>
-
-
- - +
+ <% const vsItems=(data.visaSolutions && Array.isArray(data.visaSolutions.items) && + data.visaSolutions.items.length===4) ? data.visaSolutions.items : (data.visaSolutions && + Array.isArray(data.visaSolutions.items) && data.visaSolutions.items.length> 0) + ? (function () { + const clone = data.visaSolutions.items.slice(0, 4); + while (clone.length < 4) clone.push({}); return clone; })() : [{}, {}, {}, {}]; %> + <% vsItems.forEach(function(item, index) { %> +
+
+
Service + <%= index + 1 %> +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
-
- - -
-
- - -
-
- - -
-
-
-
- <% }); %> + <% }); %>
+ + \ No newline at end of file diff --git a/views/admin/home/sections/whyChooseUs.ejs b/views/admin/home/sections/whyChooseUs.ejs index 2a1a24b..fd98ef3 100644 --- a/views/admin/home/sections/whyChooseUs.ejs +++ b/views/admin/home/sections/whyChooseUs.ejs @@ -3,7 +3,7 @@
-
+
Basic Information @@ -13,32 +13,78 @@
- + placeholder="e.g., Turning Study Abroad Dreams Into Reality" />
- + +
+
+ + + This word in the heading will be wrapped in a colored span.
- + +
+
+
+
+
+ + +
+
+
+
+ About Images (Left side) +
+
+
+
+
+ + Recommended size: 375x419px +
+ + +
+ <% if (data.whyChooseUs?.mainImage) { %> +
+ Main about image preview +
+ <% } %> +
+ +
+ + Recommended size: 376x394px +
+ + +
+ <% if (data.whyChooseUs?.secondaryImage) { %> +
+ Secondary about image preview +
+ <% } %>
@@ -55,53 +101,36 @@
<% (data.whyChooseUs?.items || []).forEach(function(item, index) { %> -
-
-
Item <%= index + 1 %>
-
-
- -
- - +
+
+
Item <%= index + 1 %> +
+
+
+ +
+ + +
+
+
+ + +
+
+ +
-
-
- - -
-
- -
-
- <% }); %> + <% }); %>
@@ -116,17 +145,12 @@
<% (data.whyChooseUs?.features || []).forEach(function(feature, index) { %> -
- - -
- <% }); %> +
+ + +
+ <% }); %>
@@ -143,23 +167,13 @@
- +
- +
@@ -167,3 +181,53 @@
+ + \ No newline at end of file From ade8fc7e08c3caf3f6d017f30be3d70d22601727 Mon Sep 17 00:00:00 2001 From: Wini_Fy Date: Thu, 5 Feb 2026 21:22:06 +0700 Subject: [PATCH 2/7] Merge branch 'main' of https://gits.techvanguard.vn/UKSOURCE/cms.hailearning.edu.vn into fea/thanh-05022026-home From 8232a36e718add89ebfd79ba27ba85886076e99e Mon Sep 17 00:00:00 2001 From: Le Nhut Huy Date: Fri, 6 Feb 2026 01:58:56 +0700 Subject: [PATCH 3/7] feat(about): sync FE-BE data and image upload --- controllers/aboutController.js | 161 --- controllers/aboutUsController.js | 474 +++------ data/about.json | 123 +++ data/aboutCamp.json | 28 - data/aboutUs.json | 149 --- models/about.js | 64 -- models/aboutUs.js | 152 +-- routes/admin.js | 14 +- routes/index.js | 11 +- scripts/migrateAboutUs.js | 48 + scripts/seedAbout.js | 52 + server.js | 3 + utils/imageHelper.js | 33 +- views/admin/about/index.ejs | 1024 ------------------- views/admin/aboutUs/index.ejs | 1578 ++++++++++++------------------ views/layouts/admin.ejs | 3 - 16 files changed, 1096 insertions(+), 2821 deletions(-) delete mode 100644 controllers/aboutController.js create mode 100644 data/about.json delete mode 100644 data/aboutCamp.json delete mode 100644 data/aboutUs.json delete mode 100644 models/about.js create mode 100644 scripts/migrateAboutUs.js create mode 100644 scripts/seedAbout.js delete mode 100644 views/admin/about/index.ejs diff --git a/controllers/aboutController.js b/controllers/aboutController.js deleted file mode 100644 index 5607453..0000000 --- a/controllers/aboutController.js +++ /dev/null @@ -1,161 +0,0 @@ -const { addBaseUrlToImages } = require('../utils/imageHelper'); -const About = require('../models/about'); - -// Get about data from MongoDB -const getAboutData = async () => { - const about = await About.findOne().sort({ updatedAt: -1 }); - - // Trả về object rỗng với cấu trúc cơ bản nếu không có dữ liệu - if (!about) { - return { - banner: { - image: '', - title: '', - text: '' - }, - about: { - title: '', - paragraphs: [], - list_items: [], - button: { - text: '', - url: '' - }, - image: '', - quote: { - mark_image: '', - title: '', - text: '', - author: '' - } - }, - values: { - background_image: '', - items: [] - }, - education: { - images: { - student1: '', - student2: '' - }, - subtitle: '', - title: '', - text: '' - }, - advantages: { - title: '', - items: [] - }, - academic_board: { - title: '', - members: [] - } - }; - } - - return about; -}; - -// Display about management page -exports.index = async (req, res) => { - try { - const data = await getAboutData(); - res.render('admin/about', { - title: 'About Management', - data - }); - } catch (err) { - console.error(err); - req.flash('error_msg', 'Error loading about data'); - res.redirect('/admin/dashboard'); - } -}; - -// Update about data -exports.update = async (req, res) => { - try { - // Lấy document hiện tại từ MongoDB - const currentData = await getAboutData(); - - // Danh sách các section cần cập nhật - const sections = ['banner', 'about', 'values', 'education', 'advantages', 'academic_board']; - const errors = []; - let hasChanges = false; - - // Tạo đối tượng dữ liệu mới dựa trên dữ liệu hiện tại - const updatedData = { ...currentData.toObject() }; - - // Xử lý từng section - sections.forEach(section => { - try { - // Kiểm tra nếu section không được gửi lên - if (!req.body[section]) { - console.warn(`No data for section: ${section}`); - return; - } - - // Parse dữ liệu JSON từ form - const newSectionData = JSON.parse(req.body[section]); - - // So sánh dữ liệu mới với dữ liệu hiện tại - const currentSectionData = currentData[section]; - const sectionHasChanges = JSON.stringify(newSectionData) !== JSON.stringify(currentSectionData); - - // Nếu có thay đổi, cập nhật vào đối tượng dữ liệu mới - if (sectionHasChanges) { - updatedData[section] = newSectionData; - hasChanges = true; - } - } catch (error) { - console.error(`Error processing section ${section}:`, error); - errors.push(`Error processing ${section} data: ${error.message}`); - } - }); - - // Nếu có lỗi, thông báo và chuyển hướng - if (errors.length > 0) { - req.flash('error_msg', `Data processing error: ${errors[0]}`); - return req.session.save(() => res.redirect('/admin/about')); - } - - // Nếu không có thay đổi, thông báo và chuyển hướng - if (!hasChanges) { - req.flash('info_msg', 'No changes were made'); - return req.session.save(() => res.redirect('/admin/about')); - } - - try { - // Cập nhật hoặc tạo mới document trong MongoDB - if (currentData._id) { - await About.findByIdAndUpdate(currentData._id, updatedData, { new: true }); - } else { - await About.create(updatedData); - } - - // Success notification and redirect - req.flash('success_msg', 'About data updated successfully'); - return req.session.save(() => res.redirect('/admin/about')); - } catch (dbError) { - console.error('Database error:', dbError); - req.flash('error_msg', `Database error: ${dbError.message || 'Unknown'}`); - return req.session.save(() => res.redirect('/admin/about')); - } - } catch (err) { - console.error('Update error:', err); - req.flash('error_msg', `Update error: ${err.message || 'Unknown'}`); - return req.session.save(() => res.redirect('/admin/about')); - } -}; - -// API to get about data -exports.api = async (req, res) => { - try { - const aboutData = await getAboutData(); - const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get('host')}`; - const processedData = addBaseUrlToImages(aboutData, baseUrl); - res.json(processedData); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'Error loading about data' }); - } -}; \ No newline at end of file diff --git a/controllers/aboutUsController.js b/controllers/aboutUsController.js index 0910c02..cf94d30 100644 --- a/controllers/aboutUsController.js +++ b/controllers/aboutUsController.js @@ -1,363 +1,141 @@ -const {addBaseUrlToImages} = require("../utils/imageHelper"); -const About = require("../models/about"); +const { addBaseUrlToImages } = require("../utils/imageHelper"); const AboutUs = require("../models/aboutUs"); +const jsonHelper = require("../utils/jsonHelper"); -// -------------------- Public (read-only) helpers -------------------- -// Map stored About document back to the original aboutUs.json shape -function transformToAboutUs(doc) { - if (!doc) return null; - - const hero = { - banner: doc.banner?.image || "", - title: doc.banner?.title || "", - breadcrumb: doc.banner?.text || "", - }; - - const stats = Array.isArray(doc.advantages?.items) - ? doc.advantages.items.map((item) => ({ - number: item.number || "", - description: item.title || "", - })) - : []; - - const services = Array.isArray(doc.about?.paragraphs) - ? doc.about.paragraphs.map((p) => ({title: "", description: p})) - : []; - - const features = Array.isArray(doc.values?.items) - ? doc.values.items.map((i) => ({ - title: i.title || "", - description: i.text || "", - icon: i.icon || "", - })) - : []; - - const events = Array.isArray(doc.academic_board?.members) - ? doc.academic_board.members.map((m) => ({ - imageUrl: m.image || "", - date: "", - title: m.title || "", - description: "", - authorName: m.name || "", - authorRole: "", - })) - : []; - - return { - hero, - stats, - services, - features, - events, - }; -} - -// Get aboutUs data: prefer AboutUs collection, fallback to transforming About -const getAboutUsData = async () => { - // Prefer stored AboutUs document - const aboutUsDoc = await AboutUs.findOne().sort({updatedAt: -1}); - if (aboutUsDoc) - return aboutUsDoc.toObject ? aboutUsDoc.toObject() : aboutUsDoc; - - // Fallback: transform legacy About document into aboutUs shape - const about = await About.findOne().sort({updatedAt: -1}); - if (!about) return null; - return transformToAboutUs(about); -}; - -// -------------------- Admin (CRUD on AboutUs model) helpers -------------------- -// Default shape for AboutUs documents (matches data/aboutUs.json) -const getDefaultAboutUsData = () => ({ - hero: {title: "", backgroundImage: ""}, - introduction: { - subtitle: "", - title: "", - description: "", - mainImage: "", - services: [], - }, - statistics: { - items: [], - }, - accommodation: { - subtitle: "", - title: "", - description: "", - features: [], - }, - activities: { - subtitle: "", - title: "", - description: "", - gallery: [], - }, - newsletter: { - imagePath: "", - title: "", - description: "", - buttonText: "", - }, - events: { - title: "", - items: [], - }, -}); - -// Get latest stored AboutUs document or default (returned as plain object) -const getStoredAboutUs = async () => { - const aboutUs = await AboutUs.findOne().sort({updatedAt: -1}); - if (!aboutUs) return getDefaultAboutUsData(); - return aboutUs.toObject ? aboutUs.toObject() : aboutUs; -}; - -// -------------------- Public exports -------------------- -// Public endpoint: return AboutUs JSON (previously rendered HTML) -exports.page = async (req, res) => { - try { - const aboutUsData = await getAboutUsData(); - const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get('host')}`; - const processed = addBaseUrlToImages(aboutUsData || {}, baseUrl); - return res.json(processed); - } catch (err) { - console.error("aboutUs.page error:", err); - return res.status(500).json({ error: "Error loading about-us data" }); - } -}; - -// API endpoint to return aboutUs JSON -exports.api = async (req, res) => { - try { - const aboutUsData = await getAboutUsData(); - const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get('host')}`; - const processed = addBaseUrlToImages(aboutUsData || {}, baseUrl); - return res.json(processed); - } catch (err) { - console.error("aboutUs.api error:", err); - return res.status(500).json({error: "Error loading about-us data"}); - } -}; - -// API endpoint to return an array of AboutUs records (for frontend listing) -exports.apiList = async (req, res) => { - try { - const docs = await AboutUs.find().sort({ updatedAt: -1 }).lean(); - const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get('host')}`; - const processed = (docs || []).map((d) => addBaseUrlToImages(d || {}, baseUrl)); - return res.json(processed); - } catch (err) { - console.error("aboutUs.apiList error:", err); - return res.status(500).json({ error: "Error loading about-us list" }); - } -}; - -// -------------------- Admin exports -------------------- -// Display AboutUs management page -exports.index = async (req, res) => { - try { - const data = await getStoredAboutUs(); - const items = await AboutUs.find().sort({updatedAt: -1}).limit(10); - - res.render("admin/aboutUs/index", { - layout: "layouts/main", - title: "About Us Management", - data, - items, - frontendUrl: - process.env.FRONTEND_URL || req.protocol + "://" + req.get("host"), - currentPath: req.path, - user: req.session.user, - }); - } catch (err) { - console.error(err); - req.flash("error_msg", "Error loading About Us data"); - res.redirect("/admin/dashboard"); - } -}; - -// Display create form -exports.createForm = async (req, res) => { - try { - const data = getDefaultAboutUsData(); - - res.render("admin/aboutUs/create", { - layout: "layouts/main", - title: "Create About Us", - data, - currentPath: req.path, - user: req.session.user, - }); - } catch (err) { - console.error(err); - req.flash("error_msg", "Error loading create form"); - res.redirect("/admin/about-us"); - } -}; - -// Create new AboutUs record -exports.create = async (req, res) => { - try { - const aboutUsData = { - hero: JSON.parse(req.body.hero || "{}"), - introduction: JSON.parse(req.body.introduction || "{}"), - statistics: JSON.parse(req.body.statistics || "{}"), - accommodation: JSON.parse(req.body.accommodation || "{}"), - activities: JSON.parse(req.body.activities || "{}"), - newsletter: JSON.parse(req.body.newsletter || "{}"), - events: JSON.parse(req.body.events || "{}"), - }; - - const newAboutUs = new AboutUs(aboutUsData); - await newAboutUs.save(); - - req.flash("success_msg", "About Us created successfully"); - res.redirect("/admin/about-us"); - } catch (err) { - console.error("Create error:", err); - req.flash("error_msg", `Create error: ${err.message || "Unknown"}`); - res.redirect("/admin/about-us/create"); - } -}; - -// Display edit form -exports.editForm = async (req, res) => { - try { - const aboutUs = await AboutUs.findById(req.params.id); - - if (!aboutUs) { - req.flash("error_msg", "About Us record not found"); - return res.redirect("/admin/about-us"); - } - - res.render("admin/aboutUs/edit", { - layout: "layouts/main", - title: "Edit About Us", - data: aboutUs.toObject ? aboutUs.toObject() : aboutUs, - currentPath: req.path, - user: req.session.user, - }); - } catch (err) { - console.error(err); - req.flash("error_msg", "Error loading edit form"); - res.redirect("/admin/about-us"); - } -}; - -// Update AboutUs record -exports.update = async (req, res) => { - try { - // Get current data - const currentData = await getStoredAboutUs(); - - // Parse form data - const sections = [ - "hero", - "introduction", - "statistics", - "accommodation", - "activities", - "newsletter", - "events", - ]; - const errors = []; - let hasChanges = false; - - // Create updated data object - const updatedData = { - ...(currentData.toObject ? currentData.toObject() : currentData), - }; - - // Process each section - sections.forEach((section) => { - try { - if (!req.body[section]) { - console.warn(`No data for section: ${section}`); - return; - } - - const newSectionData = JSON.parse(req.body[section]); - const currentSectionData = currentData[section]; - const sectionHasChanges = - JSON.stringify(newSectionData) !== JSON.stringify(currentSectionData); - - if (sectionHasChanges) { - updatedData[section] = newSectionData; - hasChanges = true; - } - } catch (error) { - console.error(`Error processing section ${section}:`, error); - errors.push(`Error processing ${section} data: ${error.message}`); - } - }); - - if (errors.length > 0) { - req.flash("error_msg", `Data processing error: ${errors[0]}`); - return req.session.save(() => res.redirect("/admin/about-us")); - } - - if (!hasChanges) { - req.flash("info_msg", "No changes were made"); - return req.session.save(() => res.redirect("/admin/about-us")); - } - +/** + * GET /api/about + * Lấy dữ liệu About Us (Public API cho website và CMS load dữ liệu) + */ +exports.getAbout = async (req, res) => { try { - // Only update existing document; do not create a new one here - if (!currentData || !currentData._id) { - req.flash("error_msg", "No existing About Us record to update. Create one first."); - return req.session.save(() => res.redirect("/admin/about-us")); - } + // Force no-cache headers + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', '0'); - await AboutUs.findByIdAndUpdate(currentData._id, updatedData, { - new: true, - }); + const data = await AboutUs.getSingle(); + const rawData = data.toObject(); - req.flash("success_msg", "About Us data updated successfully"); - return req.session.save(() => res.redirect("/admin/about-us")); - } catch (dbError) { - console.error("Database error:", dbError); - req.flash("error_msg", `Database error: ${dbError.message || "Unknown"}`); - return req.session.save(() => res.redirect("/admin/about-us")); + const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get('host')}`; + const processedData = addBaseUrlToImages(rawData, baseUrl); + + res.json(processedData); + } catch (error) { + console.error("Error getting about data:", error); + res.status(500).json({ + success: false, + error: "Failed to get about data" + }); } - } catch (err) { - console.error("Update error:", err); - req.flash("error_msg", `Update error: ${err.message || "Unknown"}`); - return req.session.save(() => res.redirect("/admin/about-us")); - } }; -// Delete AboutUs record -exports.delete = async (req, res) => { - try { - const aboutUs = await AboutUs.findById(req.params.id); +/** + * PUT /api/about + * Cập nhật dữ liệu About Us (Dùng cho AJAX từ CMS) + */ +exports.updateAbout = async (req, res) => { + try { + let updateData = req.body; - if (!aboutUs) { - req.flash("error_msg", "About Us record not found"); - return res.redirect("/admin/about-us"); + // Nếu dữ liệu gửi qua trường aboutJson (dạng string JSON) + if (updateData.aboutJson && typeof updateData.aboutJson === "string") { + try { + updateData = JSON.parse(updateData.aboutJson); + } catch (e) { + return res.status(400).json({ + success: false, + message: "Invalid JSON in aboutJson" + }); + } + } + + const doc = await AboutUs.getSingle(); + // Use .set() for better handling of nested objects/arrays in Mongoose + doc.set(updateData); + await doc.save(); + + // Fetch fresh data for syncing and returning + const finalData = await AboutUs.findOne() + .select('-_id -__v -createdAt -updatedAt') + .lean(); + + // Update about.json file to keep it in sync + jsonHelper.writeJsonFile("about", finalData); + + res.json({ + success: true, + message: "About Us updated successfully", + data: finalData + }); + } catch (error) { + console.error("Error updating about data:", error); + res.status(500).json({ + success: false, + error: "Failed to update about data: " + error.message + }); } - - await AboutUs.findByIdAndDelete(req.params.id); - - req.flash("success_msg", "About Us record deleted successfully"); - res.redirect("/admin/about-us"); - } catch (err) { - console.error("Delete error:", err); - req.flash("error_msg", `Delete error: ${err.message || "Unknown"}`); - res.redirect("/admin/about-us"); - } }; -// Preview AboutUs record -exports.preview = async (req, res) => { - try { - const aboutUs = await AboutUs.findById(req.params.id); +/** + * Render admin page (Dùng cho Admin UI) + */ +exports.index = async (req, res) => { + try { + const data = await AboutUs.getSingle(); + const rawData = data.toObject(); - if (!aboutUs) { - return res.status(404).json({error: "About Us record not found"}); + const activeTab = req.query.activeTab || "hero"; + res.render("admin/aboutUs/index", { + layout: "layouts/main", + title: "About Us Management", + data: rawData, + activeTab, + user: req.session.user, + currentPath: req.path, + frontendUrl: process.env.FRONTEND_URL || 'http://localhost:3000' + }); + } catch (err) { + console.error("Error in about index:", err); + req.flash("error_msg", "Error loading About Us page"); + res.redirect("/admin/dashboard"); } - - const processedData = addBaseUrlToImages(aboutUs.toObject()); - res.json(processedData); - } catch (err) { - console.error("Preview error:", err); - res.status(500).json({error: "Error loading preview data"}); - } }; + +/** + * Update method cho form-based submission (Admin UI - Post fallback) + */ +exports.update = async (req, res) => { + try { + let updateData = req.body; + if (updateData.aboutJson && typeof updateData.aboutJson === "string") { + try { + updateData = JSON.parse(updateData.aboutJson); + } catch (e) { + req.flash("error_msg", "Invalid JSON data"); + return res.redirect("/admin/about-us"); + } + } + + const doc = await AboutUs.getSingle(); + doc.set(updateData); + await doc.save(); + + const finalData = await AboutUs.findOne() + .select('-_id -__v -createdAt -updatedAt') + .lean(); + jsonHelper.writeJsonFile("about", finalData); + + req.flash("success_msg", "About Us updated successfully"); + const activeTab = req.query.activeTab || "hero"; + res.redirect(`/admin/about-us?activeTab=${activeTab}`); + } catch (err) { + console.error("Update error:", err); + req.flash("error_msg", "Error updating About Us: " + err.message); + res.redirect("/admin/about-us"); + } +}; + +// Aliases for compatibility +exports.api = exports.getAbout; +exports.page = exports.getAbout; +exports.updateAboutUs = exports.updateAbout; diff --git a/data/about.json b/data/about.json new file mode 100644 index 0000000..ac1e658 --- /dev/null +++ b/data/about.json @@ -0,0 +1,123 @@ +{ + "hero": { + "title": "About Us", + "breadcrumb": [ + "Home", + "About Us" + ], + "backgroundImage": "/uploads/about/breadcrumb.jpg" + }, + "intro": { + "subheading": "Company Intro", + "heading": "Building Pathways to Your Immigration Success", + "description": "We provide expert guidance, personalized solutions, and transparent processes to help you achieve your immigration goals. Our dedicated team ensures a smooth journey, building pathways to your international success.", + "image": "http://localhost:3001/uploads/about/intro.jpg" + }, + "mission": { + "subheading": "About Our Consultancy", + "heading": "Turning Study Abroad Dreams Into Reality", + "description": "We guide students with expert visa consulting, ensuring a smooth process from application to approval, turning study abroad aspirations into life-changing opportunities for a brighter future.", + "images": { + "main": "/assets/img/home-1/about/about-1.jpg", + "secondary": "/assets/img/home-1/about/about-02.jpg", + "bgShape": "/assets/img/home-1/about/Vector.png", + "planeShape": "/assets/img/home-1/about/plane.png", + "topShape": "/assets/img/home-1/about/shape.png", + "globeShape": "/assets/img/home-1/about/globe.png" + }, + "items": [ + { + "icon": "/assets/img/home-1/icon/01.svg", + "label": "Global Reach", + "description": "Expanding Opportunities Worldwide" + }, + { + "icon": "/assets/img/home-1/icon/01.svg", + "label": "Global Reach", + "description": "Expanding Opportunities Worldwide" + } + ], + "features": [ + "Fastest Visa form processing with skilled immigration agents", + "Partnership with International Educational Institutions" + ], + "ctaButton": { + "label": "Get Started", + "href": "/about" + } + }, + "features": { + "backgroundImage": "/assets/img/home-2/feature/bg-shape.png", + "subheading": "Your Travel Made Easy", + "heading": "Smooth Visa Journey Guaranteed", + "description": "We provide expert guidance for every visa application, ensuring smooth processing, personalized support, and reliable assistance", + "image": "/assets/img/home-2/feature/02.png", + "items": [ + { + "icon": "/assets/img/home-2/icon/01.png", + "title": "Expert Consultants", + "description": "Skilled and knowledgeable visa advisors. Skilled and knowledgeable visa advisors." + }, + { + "icon": "/assets/img/home-2/icon/01.png", + "title": "Personalized Support", + "description": "Skilled and knowledgeable visa advisors. Skilled and knowledgeable visa advisors." + }, + { + "icon": "/assets/img/home-2/icon/01.png", + "title": "Transparent Process", + "description": "Skilled and knowledgeable visa advisors. Skilled and knowledgeable visa advisors." + } + ], + "ctaButton": { + "label": "Get Started Today", + "href": "/contact" + } + }, + "news": { + "subheading": "Visa Tips & Guides", + "heading": "Latest Insights & Updates", + "ctaButton": { + "label": "view all articles", + "href": "/blog" + }, + "items": [ + { + "title": "Step-by-Step Guide to Applying for a Student Visa", + "category": "Student Visa", + "date": "20 August ,2025", + "comments": 8, + "author": { + "name": "Sohel", + "avatar": "/assets/img/home-1/news/client.png" + }, + "link": "/blog/step-by-step-guide-student-visa", + "thumbnail": "/assets/img/home-1/news/news-1.jpg" + }, + { + "title": "Tips to Prepare Financial Documents for Visa Approval", + "category": "IELTS / TOEFL", + "date": "20 August ,2025", + "comments": 8, + "author": { + "name": "Sohel", + "avatar": "/assets/img/home-1/news/client.png" + }, + "link": "/blog/financial-documents-visa-approval", + "thumbnail": "/assets/img/home-1/news/news-2.jpg" + }, + { + "title": "Post-Arrival Guide What Every Student Should Know", + "category": "Study Abroad", + "date": "20 August ,2025", + "comments": 8, + "author": { + "name": "Sohel", + "avatar": "/assets/img/home-1/news/client.png" + }, + "link": "/blog/post-arrival-guide-students", + "thumbnail": "/assets/img/home-1/news/news-3.jpg" + } + ] + } +} \ No newline at end of file diff --git a/data/aboutCamp.json b/data/aboutCamp.json deleted file mode 100644 index e68576e..0000000 --- a/data/aboutCamp.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "avatars": [ - "yootheme/aboutImage/profile-face_1.jpg", - "yootheme/aboutImage/young-tourist-sitting-tent.jpg", - "yootheme/aboutImage/portrait-young-male-tourist-standing-forest-with-tent.jpg" - ], - "images": { - "mainImage1": "yootheme/img/a1.jpg", - "mainImage2": "yootheme/img/a2.jpg" - }, - "content": { - "sectionTitle": "About Us", - "mainTitle": "Creating Amazing Camps", - "description": "Learning is closely tied to practical experience—summer is the perfect time for hands-on opportunities. While knowledge must still be nurtured, it can take on new and more engaging forms.", - "quote": "Your Journey, Your Comfort,\nYour Adventure.", - "authorText": "Adventurer with\nhappy customer", - "targetCount": 50 - }, - "features": [ - "Fun-Filled Experiences for Every Camper", - "Adventures That Inspire Confidence and Growth", - "Memories and Friendships That Last a Lifetime" - ], - "button": { - "label": "Learn More About", - "href": "/info/about" - } -} diff --git a/data/aboutUs.json b/data/aboutUs.json deleted file mode 100644 index f175dfe..0000000 --- a/data/aboutUs.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "hero": { - "title": "About Us", - "backgroundImage": "/uploads/about/banner.jpg" - }, - - "introduction": { - "subtitle": "Go & Grow Camp", - "title": "Go & Grow Camp A Place to Learn, Connect, and Grow", - "description": "Go & Grow Camp brings together young people from different countries and cultures to enjoy fun activities, meaningful projects, and positive community experiences. Every camper—new or returning—quickly feels included thanks to our welcoming environment and supportive team.", - "mainImage": "/uploads/about/section2.jpg", - "services": [ - { - "title": "Always Here", - "description": "Camp leaders are ready to guide and support you whenever needed." - }, - { - "title": "Fun & Learning", - "description": "Engage in exciting activities that help you grow new skills." - }, - { - "title": "Team Spirit", - "description": "Work together, take responsibility, and support each other at camp." - } - ] - }, - - "statistics": { - "items": [ - { - "number": "2K+", - "description": "Smiles and Friendships Made" - }, - { - "number": "25+", - "description": "Countries Connected" - }, - { - "number": "50+", - "description": "Adventure & Skill-Building Activities" - }, - { - "number": "20+", - "description": "Exciting Challenges Every Camp" - } - ] - }, - - "accommodation": { - "subtitle": "Accommodation", - "title": "Blending Comfort With Responsible Living", - "description": "Enjoy a tranquil atmosphere with beautiful views, modern facilities, and personal touches that make you feel at home.", - "features": [ - { - "title": "Safe Environment", - "description": "Safety is our top priority, with secure facilities and connecting with nature.", - "icon": "/uploads/about/act2.jpg" - }, - { - "title": "Family Atmosphere", - "description": "Every camper is part of our big camp family, where friendships grow and everyone feels included.", - "icon": "/uploads/about/act2.jpg" - }, - { - "title": "Cultural Exchange", - "description": "Experience diversity and learn about different cultures from campers around the world.", - "icon": "/uploads/about/act2.jpg" - }, - { - "title": "Personal Growth", - "description": "Activities encourage confidence, independence, and learning through fun challenges.", - "icon": "/uploads/about/act2.jpg" - }, - { - "title": "Creativity & Fun", - "description": "Express yourself through games, arts, and exciting hands-on experiences.", - "icon": "/uploads/about/act2.jpg" - }, - { - "title": "Creativity & Fun", - "description": "Express yourself through games, arts, and exciting hands-on experiences.", - "icon": "/uploads/about/act2.jpg" - } - ] - }, - - "activities": { - "subtitle": "Activities", - "title": "Enjoy unforgettable experiences at Go and Grow Camp", - "description": "Discover a world of adventure, creativity, and friendship. From exciting outdoor activities to hands-on workshops, every day is full of new experiences that help campers grow, have fun, and make memories that last a lifetime.", - "gallery": [ - { - "image": "/uploads/about/act1.jpg", - "title": "Outdoor Adventures", - "description": "Climb, paddle and explore with our experienced team." - }, - { - "image": "/uploads/about/act2.jpg", - "title": "Creative Workshops", - "description": "Arts & crafts sessions to spark imagination." - }, - { - "image": "/uploads/about/act3.jpg", - "title": "Water Sports", - "description": "Safe swimming and supervised water activities." - }, - { - "image": "/uploads/about/act4.jpg", - "title": "Campfire Nights", - "description": "Evening stories, music, and marshmallow roasting." - } - ] - }, - - "newsletter": { - "imagePath": "/uploads/about/newsletter.jpg", - "title": "Stay Updated with Our Monthly", - "description": "Sign up to receive the latest news about new camps, activities, and exciting opportunities. Don't miss out on anything fun!", - "buttonText": "Subscribe" - }, - - "events": { - "title": "Tour Events for you", - "items": [ - - { - "imageUrl": "/uploads/about/act1.jpg", - "date": "September 19, 2022", - "title": "The Bottom Line on Dietary Supplements", - "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sit amet tristique neque, in suscipit sem. Fusce ut tellus neque. Suspendisse ...", - "age": "Age Group: 10–14" - }, - { - "imageUrl": "/uploads/about/act2.jpg", - "date": "September 19, 2022", - "title": "The Bottom Line on Dietary Supplements", - "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sit amet tristique neque, in suscipit sem. Fusce ut tellus neque. Suspendisse ...", - "age": "Age Group: 10–14" - }, - { - "imageUrl": "/uploads/about/act3.jpg", - "date": "September 19, 2022", - "title": "The Bottom Line on Dietary Supplements", - "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris sit amet tristique neque, in suscipit sem. Fusce ut tellus neque. Suspendisse ...", - "age": "Age Group: 10–14" - } - ] - } -} \ No newline at end of file diff --git a/models/about.js b/models/about.js deleted file mode 100644 index 18dc509..0000000 --- a/models/about.js +++ /dev/null @@ -1,64 +0,0 @@ -const mongoose = require('mongoose'); - -const aboutSchema = new mongoose.Schema({ - banner: { - image: String, - title: String, - text: String - }, - about: { - title: String, - paragraphs: [String], - list_items: [String], - button: { - text: String, - url: String - }, - image: String, - quote: { - mark_image: String, - title: String, - text: String, - author: String - } - }, - values: { - background_image: String, - items: [{ - icon: String, - title: String, - text: String - }] - }, - education: { - images: { - student1: String, - student2: String - }, - subtitle: String, - title: String, - text: String - }, - advantages: { - title: String, - items: [{ - number: String, - title: String, - text: String - }] - }, - academic_board: { - title: String, - members: [{ - image: String, - title: String, - name: String, - color: String - }] - }, - updatedAt: Date -}, { - timestamps: true -}); - -module.exports = mongoose.model('About', aboutSchema); \ No newline at end of file diff --git a/models/aboutUs.js b/models/aboutUs.js index f38aa82..1c0bfda 100644 --- a/models/aboutUs.js +++ b/models/aboutUs.js @@ -2,87 +2,105 @@ const mongoose = require("mongoose"); const aboutUsSchema = new mongoose.Schema( { - // Hero section hero: { title: String, + breadcrumb: [String], backgroundImage: String, }, - - // Introduction section with nested services - introduction: { - subtitle: String, - title: String, + intro: { + subheading: String, + heading: String, description: String, - mainImage: String, - services: [ - { - title: String, - description: String, - }, - ], + image: String, }, - - // Statistics with nested items - statistics: { + mission: { + subheading: String, + heading: String, + description: String, + images: { + main: String, + secondary: String, + bgShape: String, + planeShape: String, + topShape: String, + globeShape: String, + }, items: [ - { - number: String, - description: String, - }, + new mongoose.Schema( + { + icon: String, + label: String, + description: String, + }, + { _id: false } + ), ], + features: [String], + ctaButton: { + label: String, + href: String, + }, }, - - // Accommodation section with nested features - accommodation: { - subtitle: String, - title: String, + features: { + backgroundImage: String, + subheading: String, + heading: String, description: String, - features: [ - { - title: String, - description: String, - icon: String, - }, - ], - }, - - // Activities section with nested gallery - activities: { - subtitle: String, - title: String, - description: String, - gallery: [ - { - image: String, - title: String, - description: String, - }, - ], - }, - - // Newsletter - newsletter: { - imagePath: String, - title: String, - description: String, - buttonText: String, - }, - - // Events with nested items - events: { - title: String, + image: String, items: [ - { - imageUrl: String, - date: String, - title: String, - description: String, - age: String, - }, + new mongoose.Schema( + { + icon: String, + title: String, + description: String, + }, + { _id: false } + ), + ], + ctaButton: { + label: String, + href: String, + }, + }, + news: { + subheading: String, + heading: String, + ctaButton: { + label: String, + href: String, + }, + items: [ + new mongoose.Schema( + { + title: String, + category: String, + date: String, + comments: Number, + author: { + name: String, + avatar: String, + }, + link: String, + thumbnail: String, + }, + { _id: false } + ), ], }, }, - {timestamps: true} + { + timestamps: true, + collection: "aboutus", + } ); +// Static method để đảm bảo luôn chỉ có 1 bản ghi duy nhất (Singleton) +aboutUsSchema.statics.getSingle = async function () { + let doc = await this.findOne(); + if (!doc) { + doc = await this.create({}); + } + return doc; +}; + module.exports = mongoose.model("AboutUs", aboutUsSchema); diff --git a/routes/admin.js b/routes/admin.js index 029220a..9e5bb0e 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -4,7 +4,6 @@ const { ensureAuthenticated } = require("../middleware/auth"); const dashboardController = require("../controllers/dashboardController"); const uploadController = require("../controllers/uploadController"); const homeController = require("../controllers/homeController"); -const aboutController = require("../controllers/aboutController"); const headerController = require("../controllers/headerController"); const footerController = require("../controllers/footerController"); const aboutUsController = require("../controllers/aboutUsController"); @@ -44,18 +43,9 @@ router.param("code", (req, res, next, code) => { next(); }); -// About -router.get("/about", ensureAuthenticated, aboutController.index); -router.post("/about/update", ensureAuthenticated, aboutController.update); - -// AboutUs admin CRUD +// About Us router.get("/about-us", ensureAuthenticated, aboutUsController.index); -router.get("/about-us/create", ensureAuthenticated, aboutUsController.createForm); -router.post("/about-us/create", ensureAuthenticated, aboutUsController.create); -router.get("/about-us/:id/edit", ensureAuthenticated, aboutUsController.editForm); -router.post("/about-us/:id/update", ensureAuthenticated, aboutUsController.update); -router.post("/about-us/:id/delete", ensureAuthenticated, aboutUsController.delete); -router.get("/about-us/:id/preview", ensureAuthenticated, aboutUsController.preview); +router.post("/about-us/update", ensureAuthenticated, aboutUsController.update); // Booking admin CRUD removed diff --git a/routes/index.js b/routes/index.js index ed05417..9c06203 100644 --- a/routes/index.js +++ b/routes/index.js @@ -2,7 +2,6 @@ const express = require("express"); const path = require("path"); const router = express.Router(); const homeController = require("../controllers/homeController"); -const aboutController = require("../controllers/aboutController"); const aboutUsController = require("../controllers/aboutUsController"); const headerController = require("../controllers/headerController"); const socialLinkController = require("../controllers/socialLinkController"); @@ -38,12 +37,12 @@ router.get("/", (req, res) => { router.get("/api/home", homeController.api); // API để lấy dữ liệu about -router.get("/api/about", aboutController.api); +router.get("/api/about", aboutUsController.getAbout); +router.put("/api/about", aboutUsController.updateAbout); -// Public about-us page and API (aboutUs.json flow) -router.get("/about-us", aboutUsController.page); -// Return a list/array of AboutUs records for frontend consumption -router.get("/api/about-us", aboutUsController.apiList); +// Public about-us page and API (legacy support) +router.get("/about-us", aboutUsController.getAbout); +router.get("/api/about-us", aboutUsController.getAbout); // Header API route router.get("/api/header", headerController.api); diff --git a/scripts/migrateAboutUs.js b/scripts/migrateAboutUs.js new file mode 100644 index 0000000..29b705d --- /dev/null +++ b/scripts/migrateAboutUs.js @@ -0,0 +1,48 @@ +const mongoose = require("mongoose"); +const dotenv = require("dotenv"); +const fs = require("fs"); +const path = require("path"); + +// Load environment variables +dotenv.config(); + +const AboutUs = require("../models/aboutUs"); + +const migrate = async () => { + try { + console.log("🚀 Starting About Us migration..."); + + // 1. Connect to MongoDB + await mongoose.connect(process.env.MONGODB_URI); + console.log("✅ MongoDB Connected"); + + // 2. Read about.json from Backend (Source of Truth) + const jsonPath = path.join(__dirname, "../data/about.json"); + if (!fs.existsSync(jsonPath)) { + throw new Error(`Source about.json not found at: ${jsonPath}`); + } + + const rawData = fs.readFileSync(jsonPath, "utf8"); + const jsonData = JSON.parse(rawData); + console.log("✅ Read about.json successfully"); + + // 3. Delete existing AboutUs documents (Singleton pattern) + await AboutUs.deleteMany({}); + console.log("✅ Cleared existing AboutUs collection"); + + // 4. Create new AboutUs document with JSON data + const newAboutUs = new AboutUs(jsonData); + await newAboutUs.save(); + console.log("✅ Successfully migrated about.json data to MongoDB"); + + } catch (error) { + console.error("❌ Migration failed:", error.message); + } finally { + // 5. Close connection + await mongoose.connection.close(); + console.log("👋 Database connection closed"); + process.exit(0); + } +}; + +migrate(); diff --git a/scripts/seedAbout.js b/scripts/seedAbout.js new file mode 100644 index 0000000..3e60620 --- /dev/null +++ b/scripts/seedAbout.js @@ -0,0 +1,52 @@ +const mongoose = require("mongoose"); +const dotenv = require("dotenv"); +const fs = require("fs"); +const path = require("path"); + +// Load environment variables +dotenv.config(); + +const AboutUs = require("../models/aboutUs"); + +const seedAbout = async () => { + try { + console.log("🚀 Starting About section seeding..."); + + // 1. Connect to MongoDB + if (!process.env.MONGODB_URI) { + throw new Error("MONGODB_URI is not defined in environment variables"); + } + await mongoose.connect(process.env.MONGODB_URI); + console.log("✅ MongoDB Connected"); + + // 2. Read about.json (Single Source of Truth) + const jsonPath = path.join(__dirname, "../data/about.json"); + if (!fs.existsSync(jsonPath)) { + throw new Error(`Source about.json not found at: ${jsonPath}`); + } + + const rawData = fs.readFileSync(jsonPath, "utf8"); + const jsonData = JSON.parse(rawData); + console.log("✅ Read data/about.json successfully"); + + // 3. Upsert logic (Singleton pattern) + // We look for any existing document and update it, or create a new one if none exists. + await AboutUs.findOneAndUpdate( + {}, + jsonData, + { upsert: true, new: true, setDefaultsOnInsert: true } + ); + + console.log("✅ Successfully seeded about.json data to MongoDB (Upserted)"); + + } catch (error) { + console.error("❌ Seeding failed:", error.message); + } finally { + // 4. Close connection + await mongoose.connection.close(); + console.log("👋 Database connection closed"); + process.exit(0); + } +}; + +seedAbout(); diff --git a/server.js b/server.js index 721fac3..2a187ad 100644 --- a/server.js +++ b/server.js @@ -44,6 +44,9 @@ app.use( express.static(path.join(__dirname, "assets")), ); +// Map /assets/img to public/img to support frontend paths +app.use("/assets/img", express.static(path.join(__dirname, "public", "img"))); + // Serve public folder at root (for /js, /img, etc.) app.use(express.static(path.join(__dirname, "public"))); diff --git a/utils/imageHelper.js b/utils/imageHelper.js index bce7697..a894130 100644 --- a/utils/imageHelper.js +++ b/utils/imageHelper.js @@ -4,8 +4,8 @@ * @returns {Object} - Dữ liệu đã được xử lý với đường dẫn hình ảnh đầy đủ */ function addBaseUrlToImages(data, baseUrl) { - // baseUrl can be passed explicitly (e.g., from req), otherwise fall back to env - const BACKEND_URL = baseUrl || process.env.BACKEND_URL || ""; + // Use passed baseUrl, then env var, then default to localhost:3001 + const BACKEND_URL = (baseUrl || process.env.BACKEND_URL || "http://localhost:3001").replace(/\/$/, ""); // Tạo bản sao sâu để tránh thay đổi dữ liệu gốc const processedData = JSON.parse(JSON.stringify(data)); @@ -14,16 +14,27 @@ function addBaseUrlToImages(data, baseUrl) { const processObject = (obj) => { if (!obj || typeof obj !== "object") return; - Object.keys(obj).forEach((key) => { - // Kiểm tra nếu thuộc tính chứa đường dẫn hình ảnh bắt đầu bằng /uploads/ - if (typeof obj[key] === "string" && obj[key].startsWith("/uploads/")) { - // Thêm BACKEND_URL nếu đường dẫn chưa có http - if (!obj[key].startsWith("http")) { - obj[key] = `${BACKEND_URL}${obj[key]}`; + if (Array.isArray(obj)) { + obj.forEach((item, index) => { + if (typeof item === "string" && (item.startsWith("/uploads/") || item.startsWith("/assets/img/"))) { + if (!item.startsWith("http")) { + obj[index] = `${BACKEND_URL}${item}`; + } + } else if (typeof item === "object") { + processObject(item); } - } else if (typeof obj[key] === "object") { - // Đệ quy xử lý các đối tượng và mảng lồng nhau - processObject(obj[key]); + }); + return; + } + + Object.keys(obj).forEach((key) => { + const value = obj[key]; + if (typeof value === "string" && (value.startsWith("/uploads/") || value.startsWith("/assets/img/"))) { + if (!value.startsWith("http")) { + obj[key] = `${BACKEND_URL}${value}`; + } + } else if (value && typeof value === "object") { + processObject(value); } }); }; diff --git a/views/admin/about/index.ejs b/views/admin/about/index.ejs deleted file mode 100644 index e31282a..0000000 --- a/views/admin/about/index.ejs +++ /dev/null @@ -1,1024 +0,0 @@ -
-
-
-

<%= title %>

-

Edit content displayed on About page

-
- -
- -
-
-
- - - - - - - - - -
- - -
-
- - - - -
-
-
-
-
- - -
- - -
- -
- <% data.about.paragraphs.forEach((paragraph, index) => { %> -
- - -
- <% }); %> -
- -
- - -
- -
- <% data.about.list_items.forEach((item, index) => { %> -
- - -
- <% }); %> -
- -
- - -
- - -
-
- - -
- - -
- -
-
-
- - -
-
-
- <% if (data.about.image) { %> - Section image preview - <% } %> -
-
-
- - -
-
-
-
Quote Section
-
-
-
-
- -
- - -
-
-
- - -
-
- - -
-
- - -
-
-
-
-
-
-
-
-
- - -
-
-
- -
-
- -
- - -
-
-
- <% if (data.values.background_image) { %> - Background image preview - <% } %> -
-
- - -
-
-
-
Values
- -
-
- <% data.values.items.forEach((item, index) => { %> -
-
-
-
Value Information
- -
-
-
- -
- - -
- <% if (item.icon) { %> -
- Icon preview -
- <% } %> -
-
- - -
-
- - -
-
-
-
- <% }); %> -
-
-
-
-
-
- - -
-
-
- -
-
- -
- - -
- <% if (data.education.images.student1) { %> - Student 1 image preview - <% } %> -
-
- -
- - -
-
- Enter YouTube URL or upload an image file (jpg, png, gif). -
- <% if (data.education.images.student2) { %> - <% if (data.education.images.student2.includes('youtu.be') || data.education.images.student2.includes('youtube.com')) { %> -
- -
- <% } else { %> - Student 2 image preview - <% } %> - <% } %> -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
-
- - -
-
- - -
-
- -
- <% data.advantages.items.forEach((item, index) => { %> -
-
-
-
- - -
-
- - -
-
- - -
-
- -
-
- <% }); %> -
- -
-
-
-
-
- - -
-
-
-
-
- - -
-
- - -
-
- -
- <% data.academic_board.members.forEach((member, index) => { %> -
-
-
-
- -
- - -
- <% if (member.image) { %> - Member image preview - <% } %> -
-
- - -
-
- - -
-
- - -
-
- -
-
- <% }); %> -
- -
-
-
-
-
-
-
-
- - -
- - -
-
-
-
-
- - - \ No newline at end of file diff --git a/views/admin/aboutUs/index.ejs b/views/admin/aboutUs/index.ejs index 60428d5..34770b7 100644 --- a/views/admin/aboutUs/index.ejs +++ b/views/admin/aboutUs/index.ejs @@ -16,523 +16,301 @@
+ action="/admin/about-us/update"> - - - - - - - + + + + + +
- + +
-
+
+
+
+
Hero Section
+
+
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ <% if (data.hero?.backgroundImage) { %> + + <% } %> +
+
+
+
+
+ + +
+
+
+
Introduction
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ <% if (data.intro?.image) { %> + + <% } %> +
+
+
+
+
+ + +
-
-
-
- -
- - -
+
+
Mission Section
+
+
+
+
+ +
-
- <% if (data.hero?.backgroundImage) { %> - Background image preview - <% } %> +
+ +
-
-
- - + + +
+
+ +
+
+ + +
+
+ + +
+
+ +
Images
+
+ <% ['main', 'secondary', 'bgShape', 'planeShape', 'topShape', 'globeShape'].forEach(imgKey => { %> +
+ +
+ + +
+
+ <% }) %> +
+ +
+
+
+ + +
+
+
+
+
+ + +
+
- -
+ +
-
-
-
Statistics
- -
-
- <% if (data.statistics && data.statistics.items && data.statistics.items.length > 0) { %> - <% data.statistics.items.forEach((stat, index)=> { %> -
-
-
-
- - -
-
- - -
-
- -
-
- <% }); %> - <% } %> -
+
+
Features Section
-
-
- - -
-
-
+
- - + +
- - + +
-
-
-
- - +
+ +
-
-
-
- +
+
- -
-
- <% if (data.introduction?.mainImage) { %> - Main image preview - <% } %> -
-
- -
-
-
Services
- -
-
- <% if (data.introduction?.services && data.introduction.services.length > 0) { %> - <% data.introduction.services.forEach((service, index)=> { %> -
-
-
-
- - -
-
- - -
-
- -
-
- <% }) %> - <% } %> -
-
-
-
- - -
-
-
-
Activities Header
-
-
- - -
-
- - -
-
-
-
- - -
-
- -
-
-
Gallery Items
- -
-
- <% if (data.activities?.gallery && data.activities.gallery.length > 0) { %> - <% data.activities.gallery.forEach((item, index)=> { %> - - <% }); %> - <% } %> -
-
-
-
- - -
-
-
-
Newsletter Settings
-
-
- +
+
- -
-
- <% if (data.newsletter?.imagePath) { %> - Newsletter image preview - <% } %> +
+ + +
+
+ +
-
-
- - -
-
-
-
- - -
-
-
-
- - + +
+
+
Feature Items
+
+
- -
+ +
-
-
Accommodation Section
-
-
- - -
-
- - -
-
-
-
- - -
-
- -
-
Features
- -
-
- <% if (data.accommodation?.features && data.accommodation.features.length > 0) { %> - <% data.accommodation.features.forEach((feature, index)=> { %> -
-
-
-
- - -
-
- - -
-
- -
- - -
- <% if (feature.icon) { %> - Feature image preview - <% } %> -
-
- -
-
- <% }); %> - <% } %> -
+
+
News Section
-
-
- - -
-
-
-
-
Events Section
-
-
- - -
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
-
Events
-
-
- <% if (data.events?.items && data.events.items.length > 0) { %> - <% data.events.items.forEach((event, index)=> { %> -
-
-
-
- -
- - -
- <% if (event.imageUrl) { %> - Event image preview - <% } %> -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- -
-
- <% }); %> - <% } %> -
+
@@ -540,20 +318,15 @@
- -
- -
- <% if (!data || !data._id) { %> -
- No About Us document found. To enable editing, please create an About Us record first. -
- <% } %>
@@ -573,25 +346,69 @@ form.addEventListener('submit', async function (e) { e.preventDefault(); const submitBtn = document.getElementById('submitBtn'); - submitBtn.disabled = true; - submitBtn.innerHTML = 'Saving...'; - + const originalHtml = submitBtn.innerHTML; + try { + // Collect all data into a single object updateJsonData(); - this.submit(); + // No more "Saving..." text or disabling button to keep UI "instant" + // submitBtn.disabled = true; + // submitBtn.innerHTML = 'Saving...'; + + // We'll send the individual section JSONs or construct one big one + const aboutData = { + hero: JSON.parse(document.getElementById('heroJson').value), + intro: JSON.parse(document.getElementById('introJson').value), + mission: JSON.parse(document.getElementById('missionJson').value), + features: JSON.parse(document.getElementById('featuresJson').value), + news: JSON.parse(document.getElementById('newsJson').value) + }; + + const response = await fetch('/api/about', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ aboutJson: JSON.stringify(aboutData) }) + }); + + if (!response.ok) throw new Error('Update failed'); + + const result = await response.json(); + if (result.success) { + showToast('Success', 'About Us updated successfully', 'success'); + + // Update the local state with returned data from server + // This ensures the UI is in sync with what was actually saved + if (result.data) { + originalFormData = result.data; + updateAllJsonInputs(originalFormData); + } + } else { + throw new Error(result.error || 'Failed to update'); + } } catch (error) { - console.error('Error updating data:', error); - alert('Failed to process form data. Please try again.'); - submitBtn.disabled = false; - submitBtn.innerHTML = 'Save Changes'; + console.error('Error:', error); + showToast('Error', error.message, 'error'); } }); - document.querySelectorAll('.btn-upload-image').forEach(button => { - button.addEventListener('click', function () { - const targetInput = this.dataset.targetInput; - const imageType = this.dataset.imageType; + document.body.addEventListener('click', function (e) { + const uploadBtn = e.target.closest('.btn-upload-image'); + if (uploadBtn) { + const targetInput = uploadBtn.dataset.targetInput; + const imageType = uploadBtn.dataset.imageType; openImageUploader(targetInput, imageType); + } + }); + + // Tab change listener to keep track of active tab + const tabs = document.querySelectorAll('a[data-bs-toggle="tab"]'); + tabs.forEach(tab => { + tab.addEventListener('shown.bs.tab', function (e) { + const targetId = e.target.getAttribute('href').replace('#', ''); + const activeTabInput = document.getElementById('activeTabInput'); + if (activeTabInput) { + activeTabInput.value = targetId; + } }); }); } @@ -604,12 +421,9 @@ document.body.appendChild(fileInput); function getPreviewDims(name) { - if (/hero/i.test(name)) return { h: '300px', w: '100%' }; - if (/introMainImage/i.test(name)) return { h: '120px', w: '100%' }; - if (/newsletter/i.test(name)) return { h: '100px', w: '100%' }; - if (/^eventImage_/i.test(name)) return { h: '150px', w: '100%' }; - if (/^galleryImage_/i.test(name)) return { h: '100px', w: '100%' }; - return { h: '150px', w: '100%' }; + if (/hero/i.test(name)) return { h: '250px', w: '100%' }; + if (/intro/i.test(name) || /mission/i.test(name) || /features/i.test(name)) return { h: '150px', w: '100%' }; + return { h: '120px', w: '100%' }; } fileInput.onchange = async function (e) { @@ -620,12 +434,11 @@ const formData = new FormData(); formData.append('image', file); - // Disable upload button and show loading + // Disable upload button during upload const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`); const originalBtnHtml = uploadBtn ? uploadBtn.innerHTML : 'Upload'; if (uploadBtn) { uploadBtn.disabled = true; - uploadBtn.innerHTML = 'Uploading...'; } const response = await fetch(`/admin/upload/image?imageType=${imageType}`, { @@ -684,24 +497,20 @@ if (parent) parent.appendChild(img); } - // Show success message - showToast('Success', 'Image uploaded successfully', 'success'); - - // Restore button + // Removed toast for silent upload + + // Restore button state if (uploadBtn) { uploadBtn.disabled = false; - uploadBtn.innerHTML = originalBtnHtml; } - } catch (error) { console.error('Upload error:', error); showToast('Error', 'Failed to upload image: ' + error.message, 'error'); - // Restore button + // Restore button state const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`); if (uploadBtn) { uploadBtn.disabled = false; - uploadBtn.innerHTML = uploadBtn.innerHTML.replace('Uploading...', 'Upload'); } } finally { document.body.removeChild(fileInput); @@ -714,7 +523,7 @@ function resetForm() { if (confirm('Are you sure you want to reset all changes?')) { updateAllJsonInputs(originalFormData); - location.reload(); + showToast('Reset', 'Form restored to last saved state', 'info'); } } @@ -769,64 +578,263 @@ } function updateAllJsonInputs(data) { - // Hero - document.getElementById('heroJson').value = JSON.stringify(data.hero || {}); + if (!data) return; + + // 1. Hero + const hero = data.hero || {}; + document.getElementById('heroJson').value = JSON.stringify(hero); + document.getElementById('heroTitle').value = hero.title || ''; + document.getElementById('heroBreadcrumb').value = (hero.breadcrumb || []).join(', '); + document.getElementById('heroBackgroundImage').value = hero.backgroundImage || ''; + updateImagePreview('heroBackgroundImage', hero.backgroundImage); - // Introduction - document.getElementById('introductionJson').value = JSON.stringify(data.introduction || {}); + // 2. Intro + const intro = data.intro || {}; + document.getElementById('introJson').value = JSON.stringify(intro); + document.getElementById('introSubheading').value = intro.subheading || ''; + document.getElementById('introHeading').value = intro.heading || ''; + document.getElementById('introDescription').value = intro.description || ''; + document.getElementById('introImage').value = intro.image || ''; + updateImagePreview('introImage', intro.image); - // Statistics - document.getElementById('statisticsJson').value = JSON.stringify(data.statistics || {}); + // 3. Mission + const mission = data.mission || {}; + document.getElementById('missionJson').value = JSON.stringify(mission); + document.getElementById('missionSubheading').value = mission.subheading || ''; + document.getElementById('missionHeading').value = mission.heading || ''; + document.getElementById('missionDescription').value = mission.description || ''; + document.getElementById('missionCtaLabel').value = mission.ctaButton?.label || ''; + document.getElementById('missionCtaHref').value = mission.ctaButton?.href || ''; + + ['main', 'secondary', 'bgShape', 'planeShape', 'topShape', 'globeShape'].forEach(k => { + const el = document.getElementById('missionImg_' + k); + const val = mission.images?.[k] || ''; + if (el) { + el.value = val; + updateImagePreview('missionImg_' + k, val); + } + }); + populateMissionItems(mission.items || []); + populateMissionFeatures(mission.features || []); - // Accommodation - document.getElementById('accommodationJson').value = JSON.stringify(data.accommodation || {}); + // 4. Features + const features = data.features || {}; + document.getElementById('featuresJson').value = JSON.stringify(features); + document.getElementById('featuresSubheading').value = features.subheading || ''; + document.getElementById('featuresHeading').value = features.heading || ''; + document.getElementById('featuresDescription').value = features.description || ''; + document.getElementById('featuresBgImage').value = features.backgroundImage || ''; + document.getElementById('featuresImage').value = features.image || ''; + document.getElementById('featuresCtaLabel').value = features.ctaButton?.label || ''; + document.getElementById('featuresCtaHref').value = features.ctaButton?.href || ''; + updateImagePreview('featuresBgImage', features.backgroundImage); + updateImagePreview('featuresImage', features.image); + populateFeatureItems(features.items || []); - // Activities - document.getElementById('activitiesJson').value = JSON.stringify(data.activities || {}); - - // Newsletter - document.getElementById('newsletterJson').value = JSON.stringify(data.newsletter || {}); - - // Events - document.getElementById('eventsJson').value = JSON.stringify(data.events || {}); - - // Populate dynamic content - populateServicesFromData(data.introduction?.services || []); - populateFeaturesFromData(data.accommodation?.features || []); - populateEventsFromData(data.events?.items || []); - populateGalleryFromData(data.activities?.gallery || []); - populateStatsFromData(data.statistics?.items || []); + // 5. News + const news = data.news || {}; + document.getElementById('newsJson').value = JSON.stringify(news); + document.getElementById('newsSubheading').value = news.subheading || ''; + document.getElementById('newsHeading').value = news.heading || ''; + document.getElementById('newsCtaLabel').value = news.ctaButton?.label || ''; + document.getElementById('newsCtaHref').value = news.ctaButton?.href || ''; + populateNewsItems(news.items || []); } - function addStat() { - const container = document.getElementById('statsContainer'); - const index = container.children.length; + function updateImagePreview(inputId, imagePath) { + if (!imagePath) return; + const input = document.getElementById(inputId); + if (!input) return; + + let card = input.closest('.card'); + let previewImg = card ? card.querySelector('.uploaded-preview') : null; + + if (!previewImg) { + const parent = input.closest('.input-group') || input.parentElement; + previewImg = parent ? parent.querySelector('.uploaded-preview') : null; + } + + if (previewImg) { + previewImg.src = imagePath; + } + } + + // --- Helper dynamic populations --- + + function addMissionItem() { + const container = document.getElementById('missionItemsContainer'); + const idx = container.children.length; const html = ` -
-
-
-
- - -
-
- - -
-
- -
-
- `; +
+
+
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
`; container.insertAdjacentHTML('beforeend', html); } - function removeStat(button) { - button.closest('.stat-item').remove(); + function populateMissionItems(items) { + const container = document.getElementById('missionItemsContainer'); + container.innerHTML = ''; + items.forEach((item, i) => { + addMissionItem(); + const last = container.lastElementChild; + last.querySelector(`[name="missionItemIcon_${i}"]`).value = item.icon || ''; + last.querySelector(`[name="missionItemLabel_${i}"]`).value = item.label || ''; + last.querySelector(`[name="missionItemDesc_${i}"]`).value = item.description || ''; + }); } + function addMissionFeature() { + const container = document.getElementById('missionFeaturesContainer'); + const html = ` +
+ + +
`; + container.insertAdjacentHTML('beforeend', html); + } + + function populateMissionFeatures(features) { + const container = document.getElementById('missionFeaturesContainer'); + container.innerHTML = ''; + features.forEach(f => { + addMissionFeature(); + container.lastElementChild.querySelector('input').value = f || ''; + }); + } + + function addFeatureItem() { + const container = document.getElementById('featureItemsContainer'); + const idx = container.children.length; + const html = ` +
+
+
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
`; + container.insertAdjacentHTML('beforeend', html); + } + + function populateFeatureItems(items) { + const container = document.getElementById('featureItemsContainer'); + container.innerHTML = ''; + items.forEach((item, i) => { + addFeatureItem(); + const last = container.lastElementChild; + last.querySelector(`[name="featureItemIcon_${i}"]`).value = item.icon || ''; + last.querySelector(`[name="featureItemTitle_${i}"]`).value = item.title || ''; + last.querySelector(`[name="featureItemDesc_${i}"]`).value = item.description || ''; + }); + } + + function addNewsItem() { + const container = document.getElementById('newsItemsContainer'); + const idx = container.children.length; + const html = ` +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+
+ +
+ + +
+
+
+ +
+
`; + container.insertAdjacentHTML('beforeend', html); + } + + function populateNewsItems(items) { + const container = document.getElementById('newsItemsContainer'); + container.innerHTML = ''; + items.forEach((item, i) => { + addNewsItem(); + const last = container.lastElementChild; + last.querySelector(`[name="newsItemTitle_${i}"]`).value = item.title || ''; + last.querySelector(`[name="newsItemCategory_${i}"]`).value = item.category || ''; + last.querySelector(`[name="newsItemDate_${i}"]`).value = item.date || ''; + last.querySelector(`[name="newsItemComments_${i}"]`).value = item.comments || 0; + last.querySelector(`[name="newsItemAuthorName_${i}"]`).value = item.author?.name || ''; + last.querySelector(`[name="newsItemAuthorAvatar_${i}"]`).value = item.author?.avatar || ''; + last.querySelector(`[name="newsItemLink_${i}"]`).value = item.link || ''; + last.querySelector(`[name="newsItemThumbnail_${i}"]`).value = item.thumbnail || ''; + }); + } + + + function addService() { const container = document.getElementById('servicesContainer'); const index = container.children.length; @@ -852,234 +860,93 @@ container.insertAdjacentHTML('beforeend', html); } - function removeService(button) { - button.closest('.service-item').remove(); - } - function addFeature() { - const container = document.getElementById('featuresContainer'); - const index = container.children.length; - const html = ` -
-
-
-
- - -
-
- - -
-
- -
- - -
-
-
- -
-
- `; - container.insertAdjacentHTML('beforeend', html); - - // Add event listener for new upload button inside the new feature - const newButton = container.lastElementChild.querySelector('.btn-upload-image'); - if (newButton) { - newButton.addEventListener('click', function () { - const targetInput = this.dataset.targetInput; - const imageType = this.dataset.imageType; - openImageUploader(targetInput, imageType); - }); - } - } - - function removeFeature(button) { - button.closest('.feature-item').remove(); - } - - function addEvent() { - const container = document.getElementById('eventsContainer'); - const index = container.children.length; - const html = ` -
-
-
-
- -
- - -
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- -
-
- `; - container.insertAdjacentHTML('beforeend', html); - - // Add event listener for new upload button - const newButton = container.lastElementChild.querySelector('.btn-upload-image'); - newButton.addEventListener('click', function () { - const targetInput = this.dataset.targetInput; - const imageType = this.dataset.imageType; - openImageUploader(targetInput, imageType); - }); - } - - function removeEvent(button) { - button.closest('.event-item').remove(); - } function updateJsonData() { try { // Hero const heroData = { - title: (document.getElementById('heroTitle') || {}).value?.trim() || '', - backgroundImage: (document.getElementById('heroBackgroundImage') || {}).value?.trim() || '' + title: document.getElementById('heroTitle').value.trim(), + breadcrumb: document.getElementById('heroBreadcrumb').value.split(',').map(s => s.trim()).filter(s => s !== ''), + backgroundImage: document.getElementById('heroBackgroundImage').value.trim() }; document.getElementById('heroJson').value = JSON.stringify(heroData); - // Introduction - const servicesData = Array.from(document.querySelectorAll('.service-item')) - .map((item) => { - const titleEl = item.querySelector('[name^="serviceTitle_"]') || item.querySelector('input'); - const descEl = item.querySelector('[name^="serviceDescription_"]') || item.querySelector('textarea'); - return { - title: (titleEl?.value || '').trim(), - description: (descEl?.value || '').trim() - }; - }) - .filter(s => s.title !== '' || s.description !== ''); - - const introductionData = { - subtitle: (document.getElementById('introSubtitle') || {}).value?.trim() || '', - title: (document.getElementById('introTitle') || {}).value?.trim() || '', - description: (document.getElementById('introDescription') || {}).value?.trim() || '', - mainImage: (document.getElementById('introMainImage') || {}).value?.trim() || '', - services: servicesData + // Intro + const introData = { + subheading: document.getElementById('introSubheading').value.trim(), + heading: document.getElementById('introHeading').value.trim(), + description: document.getElementById('introDescription').value.trim(), + image: document.getElementById('introImage').value.trim() }; - document.getElementById('introductionJson').value = JSON.stringify(introductionData); + document.getElementById('introJson').value = JSON.stringify(introData); - // Statistics - const statsItems = Array.from(document.querySelectorAll('.stat-item')) - .map((item) => { - const numEl = item.querySelector('[name^="statNumber_"]') || item.querySelectorAll('input')[0]; - const descEl = item.querySelector('[name^="statDescription_"]') || item.querySelectorAll('input')[1]; - return { - number: (numEl?.value || '').trim(), - description: (descEl?.value || '').trim() - }; - }) - .filter(stat => stat.number !== '' || stat.description !== ''); - const statisticsData = { items: statsItems }; - document.getElementById('statisticsJson').value = JSON.stringify(statisticsData); - - // Accommodation - const featuresItems = Array.from(document.querySelectorAll('.feature-item')) - .map((item) => { - const titleEl = item.querySelector('[name^="featureTitle_"]') || item.querySelector('input'); - const descEl = item.querySelector('[name^="featureDescription_"]') || item.querySelector('textarea'); - const iconEl = item.querySelector('[name^="featureIcon_"]') || item.querySelector('input'); - return { - title: (titleEl?.value || '').trim(), - description: (descEl?.value || '').trim(), - icon: (iconEl?.value || '').trim() - }; - }) - .filter(f => f.title !== '' || f.description !== ''); - - const accommodationData = { - subtitle: (document.getElementById('accommodationSubtitle') || {}).value?.trim() || '', - title: (document.getElementById('accommodationTitle') || {}).value?.trim() || '', - description: (document.getElementById('accommodationDescription') || {}).value?.trim() || '', - features: featuresItems + // Mission + const missionData = { + subheading: document.getElementById('missionSubheading').value.trim(), + heading: document.getElementById('missionHeading').value.trim(), + description: document.getElementById('missionDescription').value.trim(), + images: { + main: document.getElementById('missionImg_main').value.trim(), + secondary: document.getElementById('missionImg_secondary').value.trim(), + bgShape: document.getElementById('missionImg_bgShape').value.trim(), + planeShape: document.getElementById('missionImg_planeShape').value.trim(), + topShape: document.getElementById('missionImg_topShape').value.trim(), + globeShape: document.getElementById('missionImg_globeShape').value.trim() + }, + items: Array.from(document.querySelectorAll('.mission-item')).map(item => ({ + icon: item.querySelector('[name^="missionItemIcon_"]').value.trim(), + label: item.querySelector('[name^="missionItemLabel_"]').value.trim(), + description: item.querySelector('[name^="missionItemDesc_"]').value.trim() + })).filter(i => i.label !== ''), + features: Array.from(document.querySelectorAll('.mission-feature-input')).map(input => input.value.trim()).filter(v => v !== ''), + ctaButton: { + label: document.getElementById('missionCtaLabel').value.trim(), + href: document.getElementById('missionCtaHref').value.trim() + } }; - document.getElementById('accommodationJson').value = JSON.stringify(accommodationData); + document.getElementById('missionJson').value = JSON.stringify(missionData); - // Activities - const galleryItems = Array.from(document.querySelectorAll('.gallery-item')) - .map((item) => { - const imageEl = item.querySelector('[name^="galleryImage_"]') || item.querySelector('input'); - const titleEl = item.querySelector('[name^="galleryTitle_"]') || item.querySelectorAll('input')[1]; - const descEl = item.querySelector('[name^="galleryDescription_"]') || item.querySelector('textarea'); - return { - image: (imageEl?.value || '').trim(), - title: (titleEl?.value || '').trim(), - description: (descEl?.value || '').trim() - }; - }) - .filter(item => item.image !== '' || item.title !== ''); - - const activitiesData = { - subtitle: (document.getElementById('activitiesSubtitle') || {}).value?.trim() || '', - title: (document.getElementById('activitiesTitle') || {}).value?.trim() || '', - description: (document.getElementById('activitiesDescription') || {}).value?.trim() || '', - gallery: galleryItems + // Features + const featuresData = { + backgroundImage: document.getElementById('featuresBgImage').value.trim(), + subheading: document.getElementById('featuresSubheading').value.trim(), + heading: document.getElementById('featuresHeading').value.trim(), + description: document.getElementById('featuresDescription').value.trim(), + image: document.getElementById('featuresImage').value.trim(), + items: Array.from(document.querySelectorAll('.feature-item-row')).map(item => ({ + icon: item.querySelector('[name^="featureItemIcon_"]').value.trim(), + title: item.querySelector('[name^="featureItemTitle_"]').value.trim(), + description: item.querySelector('[name^="featureItemDesc_"]').value.trim() + })).filter(i => i.title !== ''), + ctaButton: { + label: document.getElementById('featuresCtaLabel').value.trim(), + href: document.getElementById('featuresCtaHref').value.trim() + } }; - document.getElementById('activitiesJson').value = JSON.stringify(activitiesData); + document.getElementById('featuresJson').value = JSON.stringify(featuresData); - // Newsletter - const newsletterData = { - imagePath: (document.getElementById('newsletterImagePath') || {}).value?.trim() || '', - title: (document.getElementById('newsletterTitle') || {}).value?.trim() || '', - description: (document.getElementById('newsletterDescription') || {}).value?.trim() || '', - buttonText: (document.getElementById('newsletterButtonText') || {}).value?.trim() || '' + // News + const newsData = { + subheading: document.getElementById('newsSubheading').value.trim(), + heading: document.getElementById('newsHeading').value.trim(), + ctaButton: { + label: document.getElementById('newsCtaLabel').value.trim(), + href: document.getElementById('newsCtaHref').value.trim() + }, + items: Array.from(document.querySelectorAll('.news-item-row')).map(item => ({ + title: item.querySelector('[name^="newsItemTitle_"]').value.trim(), + category: item.querySelector('[name^="newsItemCategory_"]').value.trim(), + date: item.querySelector('[name^="newsItemDate_"]').value.trim(), + comments: parseInt(item.querySelector('[name^="newsItemComments_"]').value) || 0, + author: { + name: item.querySelector('[name^="newsItemAuthorName_"]').value.trim(), + avatar: item.querySelector('[name^="newsItemAuthorAvatar_"]').value.trim() + }, + link: item.querySelector('[name^="newsItemLink_"]').value.trim(), + thumbnail: item.querySelector('[name^="newsItemThumbnail_"]').value.trim() + })).filter(i => i.title !== '') }; - document.getElementById('newsletterJson').value = JSON.stringify(newsletterData); - - // Events - const eventsItems = Array.from(document.querySelectorAll('.event-item')).map((item) => { - const imageEl = item.querySelector('[name^="eventImage_"]') || item.querySelector('input'); - const dateEl = item.querySelector('[name^="eventDate_"]') || item.querySelector('input[type="text"]'); - const titleEl = item.querySelector('[name^="eventTitle_"]') || item.querySelector('input'); - const descEl = item.querySelector('[name^="eventDescription_"]') || item.querySelector('textarea'); - const ageEl = item.querySelector('[name^="eventAge_"]') || item.querySelectorAll('input')[3]; - - return { - imageUrl: (imageEl?.value || '').trim(), - date: (dateEl?.value || '').trim(), - title: (titleEl?.value || '').trim(), - description: (descEl?.value || '').trim(), - age: (ageEl?.value || '').trim() - }; - }).filter(event => event.title !== '' || event.description !== ''); - - const eventsData = { - title: (document.getElementById('eventsTitle') || {}).value?.trim() || '', - items: eventsItems - }; - document.getElementById('eventsJson').value = JSON.stringify(eventsData); + document.getElementById('newsJson').value = JSON.stringify(newsData); } catch (error) { console.error('Error updating JSON data:', error); @@ -1087,190 +954,5 @@ } } - // Helpers to populate DOM lists from data - function populateStatsFromData(stats) { - const container = document.getElementById('statsContainer'); - container.innerHTML = ''; - (stats || []).forEach((stat, i) => { - addStat(); - const el = container.lastElementChild; - el.querySelector(`[name="statNumber_${i}"]`).value = stat.number || ''; - el.querySelector(`[name="statDescription_${i}"]`).value = stat.description || ''; - }); - } - function populateServicesFromData(services) { - const container = document.getElementById('servicesContainer'); - container.innerHTML = ''; - (services || []).forEach((s, i) => { - addService(); - const el = container.lastElementChild; - el.querySelector(`[name="serviceTitle_${i}"]`).value = s.title || ''; - el.querySelector(`[name="serviceDescription_${i}"]`).value = s.description || ''; - }); - } - - function populateFeaturesFromData(features) { - const container = document.getElementById('featuresContainer'); - container.innerHTML = ''; - (features || []).forEach((f, i) => { - addFeature(); - const el = container.lastElementChild; - el.querySelector(`[name="featureTitle_${i}"]`).value = f.title || ''; - el.querySelector(`[name="featureDescription_${i}"]`).value = f.description || ''; - // populate icon field and preview - const iconInput = el.querySelector(`[name="featureIcon_${i}"]`); - if (iconInput) iconInput.value = f.icon || ''; - try { - let preview = el.querySelector('.uploaded-preview'); - const iconPath = f.icon || ''; - if (iconPath) { - const previewUrl = (iconPath.startsWith('http://') || iconPath.startsWith('https://')) ? iconPath : (window.location.origin + iconPath); - if (preview) { - preview.src = previewUrl; - preview.style.display = ''; - } else if (iconInput) { - const parent = iconInput.parentElement || iconInput.closest('.input-group') || iconInput.closest('div'); - if (parent) { - const img = document.createElement('img'); - img.src = previewUrl; - img.className = 'img-thumbnail uploaded-preview mt-2'; - img.style.height = '100px'; - img.style.width = '100%'; - img.style.objectFit = 'cover'; - parent.insertAdjacentElement('afterend', img); - } - } - } else if (preview) { - preview.remove(); - } - } catch (err) { - console.error('populateFeaturesFromData preview error', err); - } - }); - } - - function populateEventsFromData(events) { - const container = document.getElementById('eventsContainer'); - container.innerHTML = ''; - (events || []).forEach((ev, i) => { - addEvent(); - const el = container.lastElementChild; - el.querySelector(`[name="eventImage_${i}"]`).value = ev.imageUrl || ''; - el.querySelector(`[name="eventDate_${i}"]`).value = ev.date || ''; - el.querySelector(`[name="eventTitle_${i}"]`).value = ev.title || ''; - el.querySelector(`[name="eventDescription_${i}"]`).value = ev.description || ''; - el.querySelector(`[name="eventAge_${i}"]`).value = ev.age || ''; - // ensure preview exists or is updated for this event - try { - const input = el.querySelector(`[name="eventImage_${i}"]`); - const parent = input.parentElement || input.closest('.input-group') || input.closest('div'); - let preview = el.querySelector('.uploaded-preview'); - const imagePath = ev.imageUrl || ''; - if (imagePath) { - const previewUrl = (imagePath.startsWith('http://') || imagePath.startsWith('https://')) ? imagePath : (window.location.origin + imagePath); - if (preview) { - preview.src = previewUrl; - preview.style.display = ''; - } else if (parent) { - const img = document.createElement('img'); - img.src = previewUrl; - img.className = 'img-thumbnail uploaded-preview mt-2'; - img.style.height = '150px'; - img.style.width = '100%'; - img.style.objectFit = 'cover'; - parent.insertAdjacentElement('afterend', img); - } - } else if (preview) { - preview.remove(); - } - } catch (err) { - console.error('populateEventsFromData preview error', err); - } - }); - } - - function populateGalleryFromData(gallery) { - const container = document.getElementById('activitiesGalleryContainer'); - container.innerHTML = ''; - (gallery || []).forEach((item, i) => { - addGalleryItem(); - const el = container.lastElementChild; - el.querySelector(`[name="galleryImage_${i}"]`).value = item.image || ''; - el.querySelector(`[name="galleryTitle_${i}"]`).value = item.title || ''; - el.querySelector(`[name="galleryDescription_${i}"]`).value = item.description || ''; - // ensure preview exists or is updated - try { - const input = el.querySelector(`[name="galleryImage_${i}"]`); - const parent = input.parentElement || input.closest('.input-group') || input.closest('div'); - let preview = el.querySelector('.uploaded-preview'); - const imagePath = item.image || ''; - if (imagePath) { - const previewUrl = (imagePath.startsWith('http://') || imagePath.startsWith('https://')) ? imagePath : (window.location.origin + imagePath); - if (preview) { - preview.src = previewUrl; - preview.style.display = ''; - } else if (parent) { - const img = document.createElement('img'); - img.src = previewUrl; - img.className = 'img-thumbnail uploaded-preview mt-2'; - img.style.height = '100px'; - img.style.width = '100%'; - img.style.objectFit = 'cover'; - parent.insertAdjacentElement('afterend', img); - } - } else if (preview) { - preview.remove(); - } - } catch (err) { - console.error('populateGalleryFromData preview error', err); - } - }); - } - - function addGalleryItem() { - const container = document.getElementById('activitiesGalleryContainer'); - const index = container.children.length; - const html = ` - - `; - container.insertAdjacentHTML('beforeend', html); - - // Add event listener for new upload button - const newButton = container.lastElementChild.querySelector('.btn-upload-image'); - newButton.addEventListener('click', function () { - const targetInput = this.dataset.targetInput; - const imageType = this.dataset.imageType; - openImageUploader(targetInput, imageType); - }); - } - - function removeGalleryItem(button) { - button.closest('.gallery-item').remove(); - } \ No newline at end of file diff --git a/views/layouts/admin.ejs b/views/layouts/admin.ejs index b132781..e05bac5 100644 --- a/views/layouts/admin.ejs +++ b/views/layouts/admin.ejs @@ -60,9 +60,6 @@ - From e188eb4abebf789597b5325f10908530085d6bfb Mon Sep 17 00:00:00 2001 From: Le Nhut Huy Date: Fri, 6 Feb 2026 11:33:00 +0700 Subject: [PATCH 4/7] refactor(header): improve code formatting and add JSON response support --- assets/css/components/table.css | 29 ++++++++-- controllers/headerMenuController.js | 90 +++++++++++++++++++---------- views/admin/header/index.ejs | 31 ++++++++-- views/admin/header/menu.ejs | 2 +- 4 files changed, 112 insertions(+), 40 deletions(-) diff --git a/assets/css/components/table.css b/assets/css/components/table.css index f77f7a5..4aede1d 100644 --- a/assets/css/components/table.css +++ b/assets/css/components/table.css @@ -35,7 +35,28 @@ font-weight: var(--font-weight-medium); } -.bg-soft-success { background-color: var(--success-soft); color: var(--success-color); border: 1px solid rgba(40, 167, 69, 0.2); } -.bg-soft-danger { background-color: var(--danger-soft); color: var(--danger-color); border: 1px solid rgba(220, 53, 69, 0.2); } -.bg-soft-warning { background-color: var(--warning-soft); color: var(--warning-color); border: 1px solid rgba(255, 193, 7, 0.2); } -.bg-soft-info { background-color: var(--info-soft); color: var(--info-color); border: 1px solid rgba(23, 162, 184, 0.2); } +.bg-soft-success { + background-color: var(--success-soft); + color: var(--success-color); + border: 1px solid rgba(40, 167, 69, 0.2); +} +.bg-soft-danger { + background-color: var(--danger-soft); + color: var(--danger-color); + border: 1px solid rgba(220, 53, 69, 0.2); +} +.bg-soft-warning { + background-color: var(--warning-soft); + color: var(--warning-color); + border: 1px solid rgba(255, 193, 7, 0.2); +} +.bg-soft-info { + background-color: var(--info-soft); + color: var(--info-color); + border: 1px solid rgba(23, 162, 184, 0.2); +} +.bg-soft-secondary { + background-color: #f8f9fa; + color: #6c757d; + border: 1px solid rgba(108, 117, 125, 0.2); +} diff --git a/controllers/headerMenuController.js b/controllers/headerMenuController.js index 43c246a..5423ed4 100644 --- a/controllers/headerMenuController.js +++ b/controllers/headerMenuController.js @@ -6,13 +6,13 @@ const slugify = require("slugify"); */ const buildMenuTree = (items, parentId = null, isPublic = false) => { const branch = []; - const children = items.filter(item => - String(item.parentId) === String(parentId) || (item.parentId === null && parentId === null) + const children = items.filter( + (item) => String(item.parentId) === String(parentId) || (item.parentId === null && parentId === null), ); for (const child of children) { const item = child.toObject ? child.toObject() : { ...child }; - + // Clean data for public API if requested let cleanItem = item; if (isPublic) { @@ -20,7 +20,7 @@ const buildMenuTree = (items, parentId = null, isPublic = false) => { id: item._id, title: item.title, url: item.url, - type: item.type + type: item.type, }; } @@ -57,8 +57,8 @@ exports.index = async (req, res) => { // 2. Create Menu Item exports.store = async (req, res) => { try { - console.log('=== BACKEND: store hit ==='); - console.log('Body:', req.body); + console.log("=== BACKEND: store hit ==="); + console.log("Body:", req.body); const { title, url, parentId, order, status, type } = req.body; const slug = slugify(title, { lower: true, strict: true }); @@ -69,15 +69,27 @@ exports.store = async (req, res) => { parentId: parentId || null, order: order || 0, status: status || "active", - type: type || "internal" + type: type || "internal", }); const savedItem = await newItem.save(); - console.log('=== MENU CREATED ===', savedItem); + console.log("=== MENU CREATED ===", savedItem); + + // Return JSON for AJAX requests + if (req.xhr || req.headers.accept?.indexOf("json") > -1) { + return res.json({ success: true, message: "Menu item created successfully", data: savedItem }); + } + req.flash("success_msg", "Menu item created successfully"); res.redirect("/admin/header?tab=menu"); } catch (error) { - console.error('=== CREATE MENU ERROR ===', error); + console.error("=== CREATE MENU ERROR ===", error); + + // Return JSON for AJAX requests + if (req.xhr || req.headers.accept?.indexOf("json") > -1) { + return res.status(400).json({ success: false, message: error.message }); + } + req.flash("error_msg", "Failed to create menu item: " + error.message); res.redirect("/admin/header?tab=menu"); } @@ -87,16 +99,16 @@ exports.store = async (req, res) => { exports.update = async (req, res) => { try { const { id } = req.params; - console.log('=== BACKEND: update hit ===', { id }); - console.log('Body:', req.body); + console.log("=== BACKEND: update hit ===", { id }); + console.log("Body:", req.body); const { title, url, parentId, order, status, type } = req.body; - - const updateData = { - url, - parentId: parentId || null, - order, - status, - type + + const updateData = { + url, + parentId: parentId || null, + order, + status, + type, }; if (title) { @@ -105,17 +117,35 @@ exports.update = async (req, res) => { } const updated = await HeaderMenu.findByIdAndUpdate(id, updateData, { new: true }); - + if (!updated) { - console.log('=== UPDATE MENU NOT FOUND ===', id); + console.log("=== UPDATE MENU NOT FOUND ===", id); + + // Return JSON for AJAX requests + if (req.xhr || req.headers.accept?.indexOf("json") > -1) { + return res.status(404).json({ success: false, message: "Menu item not found" }); + } + req.flash("error_msg", "Menu item not found"); } else { - console.log('=== MENU UPDATED ===', updated); + console.log("=== MENU UPDATED ===", updated); + + // Return JSON for AJAX requests + if (req.xhr || req.headers.accept?.indexOf("json") > -1) { + return res.json({ success: true, message: "Menu item updated successfully", data: updated }); + } + req.flash("success_msg", "Menu item updated successfully"); } res.redirect("/admin/header?tab=menu"); } catch (error) { - console.error('=== UPDATE MENU ERROR ===', error); + console.error("=== UPDATE MENU ERROR ===", error); + + // Return JSON for AJAX requests + if (req.xhr || req.headers.accept?.indexOf("json") > -1) { + return res.status(400).json({ success: false, message: error.message }); + } + req.flash("error_msg", "Update failed: " + error.message); res.redirect("/admin/header?tab=menu"); } @@ -126,16 +156,16 @@ exports.destroy = async (req, res) => { try { const { id } = req.body; const menuId = id || req.params.id; - console.log('=== BACKEND: destroy hit ===', { menuId, body: req.body }); + console.log("=== BACKEND: destroy hit ===", { menuId, body: req.body }); await deleteRecursive(menuId); await HeaderMenu.findByIdAndDelete(menuId); - console.log('=== MENU DELETED ===', menuId); + console.log("=== MENU DELETED ===", menuId); req.flash("success_msg", "Menu item and its sub-menu deleted successfully"); res.redirect("/admin/header?tab=menu"); } catch (error) { - console.error('=== DELETE MENU ERROR ===', error); + console.error("=== DELETE MENU ERROR ===", error); req.flash("error_msg", "Delete failed: " + error.message); res.redirect("/admin/header?tab=menu"); } @@ -145,18 +175,18 @@ exports.destroy = async (req, res) => { exports.reorder = async (req, res) => { try { const { items } = req.body; // Array of { id, order, parentId } - + if (items && Array.isArray(items)) { - const bulkOps = items.map(item => ({ + const bulkOps = items.map((item) => ({ updateOne: { filter: { _id: item.id }, - update: { order: item.order, parentId: item.parentId || null } - } + update: { order: item.order, parentId: item.parentId || null }, + }, })); await HeaderMenu.bulkWrite(bulkOps); return res.json({ success: true, message: "Reordered successfully" }); } - + res.status(400).json({ success: false, message: "Invalid data" }); } catch (error) { res.status(500).json({ success: false, message: error.message }); diff --git a/views/admin/header/index.ejs b/views/admin/header/index.ejs index 8fe3439..03cdb93 100644 --- a/views/admin/header/index.ejs +++ b/views/admin/header/index.ejs @@ -268,6 +268,11 @@ tab.addEventListener('shown.bs.tab', function (event) { const targetId = event.target.getAttribute('href').substring(1); document.getElementById('activeTabInput').value = targetId; + + // Update URL without reload to preserve tab state + const url = new URL(window.location); + url.searchParams.set('tab', targetId); + window.history.replaceState({}, '', url); // Only load Menu Tree if clicking on the menu tab if (targetId === 'menu') { @@ -365,6 +370,12 @@ showNotification('All changes saved successfully', 'success'); submitBtn.classList.remove('btn-primary'); submitBtn.classList.add('btn-outline-primary'); + + // Reload to refresh data, preserve current tab + const currentTab = document.getElementById('activeTabInput').value; + setTimeout(() => { + window.location.href = window.location.pathname + '?tab=' + currentTab; + }, 1000); } else { const errorMsg = (!headerResult.success ? headerResult.message : '') || (!menuResult.success ? menuResult.message : '') || 'Unable to save some changes'; showNotification('Error: ' + errorMsg, 'error'); @@ -1113,19 +1124,29 @@ console.log('Response:', response.data); if (response.data.success || response.status === 200) { - showToast('Success', 'Menu information has been updated', 'success'); + showNotification('Menu item saved successfully', 'success'); + // Hide modal const modalElement = document.getElementById('modalAddMenu'); const modal = bootstrap.Modal.getOrCreateInstance(modalElement); modal.hide(); - // Refresh data or reload tab - setTimeout(() => window.location.reload(), 1000); + + // Mark as changed so user needs to click Save Changes + if (typeof window.markHeaderChanged === 'function') { + window.markHeaderChanged(); + } + + // Reload page to show updated menu structure, preserve current tab + const currentTab = document.getElementById('activeTabInput').value; + setTimeout(() => { + window.location.href = window.location.pathname + '?tab=' + currentTab; + }, 1000); } else { - showToast('Error', response.data.message || 'Unable to save menu', 'error'); + showNotification(response.data.message || 'Unable to save menu', 'error'); } } catch (error) { console.error('AJAX Error:', error); - showToast('Error', 'Server connection error: ' + (error.response?.data?.message || error.message), 'error'); + showNotification('Server connection error: ' + (error.response?.data?.message || error.message), 'error'); } finally { if (submitBtn) { submitBtn.disabled = false; diff --git a/views/admin/header/menu.ejs b/views/admin/header/menu.ejs index 0c4104e..1aa6c0b 100644 --- a/views/admin/header/menu.ejs +++ b/views/admin/header/menu.ejs @@ -38,7 +38,7 @@ External <% } %> <% if (item.status === 'inactive') { %> - Inactive + Inactive <% } else { %> Active <% } %> From cc675f85a7aa51775306795adf74c4d5f17080be Mon Sep 17 00:00:00 2001 From: Wini_Fy Date: Fri, 6 Feb 2026 12:25:49 +0700 Subject: [PATCH 5/7] feat: Update .env.example --- .env.example | Bin 528 -> 596 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.env.example b/.env.example index 63393d6a0c1a6398182a7cef112168088b095310..18f583a58ec7217bc8891522feb35b9462197a30 100644 GIT binary patch delta 156 zcmbQha)o8WGR{PX6oy=eOoqIPOBDs-90o%MBL-szlZi)FO^X?<81#WK8K|z9L60GW tArYuPhanY67cu04^fIIa#Z!SMl>+H9AYXqnHzPu4IRgk1t22;sKLCBtAhiGh delta 88 zcmcb@GJ$2nvWXiMCO%S8v|`W)!W@QthGd3BAe{kZ6*H7DSTPtg7yzN+WNSuMf~pKA IPi5Q>0KMfCb^rhX From b3bef037c4c1d96e0585236e1db807aeaf27edf1 Mon Sep 17 00:00:00 2001 From: r2xrzh9q2z-lab Date: Fri, 6 Feb 2026 14:53:45 +0700 Subject: [PATCH 6/7] update contact img --- views/admin/visa/index.ejs | 39 ++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/views/admin/visa/index.ejs b/views/admin/visa/index.ejs index 8ebd107..efefb65 100644 --- a/views/admin/visa/index.ejs +++ b/views/admin/visa/index.ejs @@ -362,6 +362,19 @@
+
+ +
+ + +
+ +
+ +
+
@@ -452,18 +465,20 @@ // Image upload handler function setupImageUploadHandlers() { - const imageInputs = ["fileFlagInput", "fileDetailMainInput", "fileGalleryInput1", "fileGalleryInput2"]; + const imageInputs = ["fileFlagInput", "fileDetailMainInput", "fileGalleryInput1", "fileGalleryInput2", "fileContactImageInput"]; const previewMap = { fileFlagInput: "preview_icon", fileDetailMainInput: "preview_main_detail", fileGalleryInput1: "preview_bannerImageGallery1", - fileGalleryInput2: "preview_bannerImageGallery2" + fileGalleryInput2: "preview_bannerImageGallery2", + fileContactImageInput: "preview_contact_image" }; const inputMap = { fileFlagInput: "icon_input", fileDetailMainInput: "mainImage_detail", fileGalleryInput1: "bannerImageGallery1", - fileGalleryInput2: "bannerImageGallery2" + fileGalleryInput2: "bannerImageGallery2", + fileContactImageInput: "contact_image_input" }; imageInputs.forEach((fileId) => { @@ -758,6 +773,17 @@ const contactInfo = country.detailedView?.contactInfo; if (contactInfo) { + document.querySelector('input[name="contact_image"]').value = contactInfo.img || ""; + + // Update contact image preview + if (contactInfo.img) { + const contactPreview = document.getElementById("preview_contact_image"); + if (contactPreview) { + contactPreview.src = contactInfo.img; + contactPreview.style.display = "block"; + } + } + document.querySelector('input[name="contact_image"]').value = contactInfo.img || ""; document.querySelector('input[name="contact_sectionTitle"]').value = contactInfo.sectionTitle || ""; document.querySelector('input[name="contact_helpText"]').value = contactInfo.helpText || ""; document.querySelector('input[name="contact_phone_label"]').value = contactInfo.phone?.label || ""; @@ -766,6 +792,7 @@ document.querySelector('input[name="contact_email"]').value = contactInfo.email?.value || ""; document.querySelector('input[name="contact_location_label"]').value = contactInfo.location?.label || ""; document.querySelector('input[name="contact_location"]').value = contactInfo.location?.address || ""; + } } @@ -843,6 +870,7 @@ document.getElementById("preview_main_detail").style.display = "none"; document.getElementById("preview_bannerImageGallery1").style.display = "none"; document.getElementById("preview_bannerImageGallery2").style.display = "none"; + document.getElementById("preview_contact_image").style.display = "none"; // Clear related countries previews for (let i = 0; i < 7; i++) { @@ -858,12 +886,13 @@ relatedNameInputs.forEach(input => input.value = ""); relatedIconInputs.forEach(input => input.value = ""); - const contactFields = ["contact_sectionTitle", "contact_helpText", "contact_phone_label", "contact_phone", "contact_email_label", "contact_email", "contact_location_label", "contact_location"]; + const contactFields = ["contact_image", "contact_sectionTitle", "contact_helpText", "contact_phone_label", "contact_phone", "contact_email_label", "contact_email", "contact_location_label", "contact_location"]; contactFields.forEach(fieldName => { const field = document.querySelector(`input[name="${fieldName}"]`); if (field) field.value = ""; }); + showFormView(); }); @@ -954,6 +983,7 @@ icon: document.getElementById(`related_url_${index}`)?.value || "" })), contactInfo: { + img: document.querySelector('input[name="contact_image"]').value, sectionTitle: document.querySelector('input[name="contact_sectionTitle"]').value, helpText: document.querySelector('input[name="contact_helpText"]').value, phone: { @@ -971,6 +1001,7 @@ } } }; + console.log('Payload:', payload); try { btnSave.disabled = true; From a0b94d8fb181b4d633f2085c92f54274d69b583c Mon Sep 17 00:00:00 2001 From: Nguyen Quang Huy Date: Fri, 6 Feb 2026 23:25:29 +0700 Subject: [PATCH 7/7] del docker and rm cache package-lock.json --- .env.example | Bin 596 -> 618 bytes Dockerfile | 17 - docker-compose.yml | 13 - package-lock.json | 2891 -------------------------------------------- 4 files changed, 2921 deletions(-) delete mode 100644 Dockerfile delete mode 100644 docker-compose.yml delete mode 100644 package-lock.json diff --git a/.env.example b/.env.example index 18f583a58ec7217bc8891522feb35b9462197a30..ad8f4a57d121c71c29c2d383ef1b81ccb4733468 100644 GIT binary patch delta 40 ncmcb@@``1Gn79XnKSMA>2!kzy0fQa_Zxnvd$Y?v6oyivfvwR37 delta 20 bcmaFGa)o7r*hYgljI0U_whRiBWtn^dN$3T< diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 41c2d15..0000000 --- a/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM node:18-alpine - -RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app - -WORKDIR /home/node/app - -COPY --chown=node:node package*.json ./ - -USER node - -RUN npm install - -COPY --chown=node:node . . - -EXPOSE 3000 - -CMD [ "npm", "start" ] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 5ced9db..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: '3.8' - -services: - app_cms_ggcamp: - build: . - container_name: my_node_app_cms_ggcamp - restart: unless-stopped - environment: - - NODE_ENV=production - volumes: - - .:/home/node/app - - /home/node/app/node_modules - network_mode: host diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 18dd2b6..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2891 +0,0 @@ -{ - "name": "cms-sims", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "cms-sims", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@editorjs/editorjs": "^2.30.8", - "@editorjs/header": "^2.8.8", - "@editorjs/image": "^2.10.3", - "@editorjs/list": "^2.0.8", - "@editorjs/marker": "^1.4.0", - "@editorjs/paragraph": "^2.11.7", - "@editorjs/quote": "^2.7.6", - "axios": "^1.10.0", - "bcryptjs": "^2.4.3", - "connect-flash": "^0.1.1", - "cookie-parser": "^1.4.6", - "dotenv": "^16.0.3", - "ejs": "^3.1.9", - "express": "^4.18.2", - "express-ejs-layouts": "^2.5.1", - "express-session": "^1.17.3", - "i18n": "^0.15.1", - "mongoose": "^8.16.1", - "multer": "^1.4.5-lts.1", - "node-fetch": "^2.6.7", - "passport": "^0.6.0", - "passport-local": "^1.0.0", - "sharp": "^0.34.4", - "slugify": "^1.6.6" - }, - "devDependencies": { - "nodemon": "^2.0.22" - } - }, - "node_modules/@codexteam/icons": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.0.5.tgz", - "integrity": "sha512-s6H2KXhLz2rgbMZSkRm8dsMJvyUNZsEjxobBEg9ztdrb1B2H3pEzY6iTwI4XUPJWJ3c3qRKwV4TrO3J5jUdoQA==", - "license": "MIT" - }, - "node_modules/@editorjs/dom": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@editorjs/dom/-/dom-0.0.5.tgz", - "integrity": "sha512-SZ78Gwpkp3EUhjBIp0lSojeQ35V9acF8SubJsMeOH/vlOUE40GOnvvwWZnF05lO7bIB0dOHhhJy4N7IIAWxP2w==", - "license": "MIT", - "dependencies": { - "@editorjs/helpers": "^0.0.4" - } - }, - "node_modules/@editorjs/editorjs": { - "version": "2.30.8", - "resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.30.8.tgz", - "integrity": "sha512-ClFuxI1qZTfXPJTacQfsJtOUP6bKoIe6BQNdAvGsDTDVwMnZEzoaSOwvUpdZEE56xppVfQueNK/1MElV9SJKHg==", - "license": "Apache-2.0" - }, - "node_modules/@editorjs/header": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/@editorjs/header/-/header-2.8.8.tgz", - "integrity": "sha512-bsMSs34u2hoi0UBuRoc5EGWXIFzJiwYgkFUYQGVm63y5FU+s8zPBmVx5Ip2sw1xgs0fqfDROqmteMvvmbCy62w==", - "license": "MIT", - "dependencies": { - "@codexteam/icons": "^0.0.5", - "@editorjs/editorjs": "^2.29.1" - } - }, - "node_modules/@editorjs/helpers": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@editorjs/helpers/-/helpers-0.0.4.tgz", - "integrity": "sha512-ieg3dzo2m1/ELze/RMNADiAiC5amXxIlVXoJ5vvXITOu/p/dPsrF+Oi3h5gBYvtGk9vg5LJUSG5YWU0tBUO1tw==", - "license": "MIT" - }, - "node_modules/@editorjs/image": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@editorjs/image/-/image-2.10.3.tgz", - "integrity": "sha512-ekCsGICZOIdghF/U2T34H7CItqaWAoJDXbkRD+x8l/LIo/7Ozf7KovYm21qz+CluArgV4RurVFHqwlz+O0vfJA==", - "license": "MIT", - "dependencies": { - "@codexteam/icons": "^0.3.0" - } - }, - "node_modules/@editorjs/image/node_modules/@codexteam/icons": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.3.3.tgz", - "integrity": "sha512-cp7mkZPgmBuSxigTm3Vb+DtVHYeX7qXfQd7o05vcLD8Ag5WvRlol2QSn5P10k0CDAJwmkH9nQGQLBycErS9lsQ==", - "license": "MIT" - }, - "node_modules/@editorjs/list": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@editorjs/list/-/list-2.0.8.tgz", - "integrity": "sha512-/EJNvpeJYa1YDtYa85ug9R3sbDyI5ZlMHQ2bIh5S9iPa19qXe45jk6kgcew+jMNQq3j4NS4Q6YeeFbU1QkNMWw==", - "license": "MIT", - "dependencies": { - "@codexteam/icons": "^0.3.2" - } - }, - "node_modules/@editorjs/list/node_modules/@codexteam/icons": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.3.3.tgz", - "integrity": "sha512-cp7mkZPgmBuSxigTm3Vb+DtVHYeX7qXfQd7o05vcLD8Ag5WvRlol2QSn5P10k0CDAJwmkH9nQGQLBycErS9lsQ==", - "license": "MIT" - }, - "node_modules/@editorjs/marker": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@editorjs/marker/-/marker-1.4.0.tgz", - "integrity": "sha512-5ipEXfL44jTTRzgNp/p/YjMO7jT08S5z4V8qA3FFJTfdhKgQyM3mvP1zpdYKw47ZZpVWMCncvk5Nto3BxihEtg==", - "license": "MIT", - "dependencies": { - "@codexteam/icons": "^0.0.5" - } - }, - "node_modules/@editorjs/paragraph": { - "version": "2.11.7", - "resolved": "https://registry.npmjs.org/@editorjs/paragraph/-/paragraph-2.11.7.tgz", - "integrity": "sha512-qD6bbWvRc4VvP0mXDOm+hOhzzhUYR9ZjcAvgCuKWcCbUMpCvhVF1s8NX40zdjekPi6JEnuHTamCncTrSzVsVhw==", - "license": "MIT", - "dependencies": { - "@codexteam/icons": "^0.0.4" - } - }, - "node_modules/@editorjs/paragraph/node_modules/@codexteam/icons": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.0.4.tgz", - "integrity": "sha512-V8N/TY2TGyas4wLrPIFq7bcow68b3gu8DfDt1+rrHPtXxcexadKauRJL6eQgfG7Z0LCrN4boLRawR4S9gjIh/Q==", - "license": "MIT" - }, - "node_modules/@editorjs/quote": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/@editorjs/quote/-/quote-2.7.6.tgz", - "integrity": "sha512-D01KUMSDj2r+6Z+xjDkQqI+y6URpeHCvj0+P4pah+GtkG040lWjFb2H4pgHFXuol2cbfyAoraYSw85fuPheCvw==", - "license": "MIT", - "dependencies": { - "@codexteam/icons": "^0.3.2", - "@editorjs/dom": "^0.0.5" - } - }, - "node_modules/@editorjs/quote/node_modules/@codexteam/icons": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.3.3.tgz", - "integrity": "sha512-cp7mkZPgmBuSxigTm3Vb+DtVHYeX7qXfQd7o05vcLD8Ag5WvRlol2QSn5P10k0CDAJwmkH9nQGQLBycErS9lsQ==", - "license": "MIT" - }, - "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", - "cpu": [ - "ppc64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", - "cpu": [ - "riscv64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", - "cpu": [ - "ppc64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", - "cpu": [ - "riscv64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@messageformat/core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@messageformat/core/-/core-3.4.0.tgz", - "integrity": "sha512-NgCFubFFIdMWJGN5WuQhHCNmzk7QgiVfrViFxcS99j7F5dDS5EP6raR54I+2ydhe4+5/XTn/YIEppFaqqVWHsw==", - "license": "MIT", - "dependencies": { - "@messageformat/date-skeleton": "^1.0.0", - "@messageformat/number-skeleton": "^1.0.0", - "@messageformat/parser": "^5.1.0", - "@messageformat/runtime": "^3.0.1", - "make-plural": "^7.0.0", - "safe-identifier": "^0.4.1" - } - }, - "node_modules/@messageformat/date-skeleton": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@messageformat/date-skeleton/-/date-skeleton-1.1.0.tgz", - "integrity": "sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==", - "license": "MIT" - }, - "node_modules/@messageformat/number-skeleton": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz", - "integrity": "sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==", - "license": "MIT" - }, - "node_modules/@messageformat/parser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.1.tgz", - "integrity": "sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==", - "license": "MIT", - "dependencies": { - "moo": "^0.5.1" - } - }, - "node_modules/@messageformat/runtime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@messageformat/runtime/-/runtime-3.0.1.tgz", - "integrity": "sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==", - "license": "MIT", - "dependencies": { - "make-plural": "^7.0.0" - } - }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", - "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", - "license": "MIT", - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", - "license": "MIT" - }, - "node_modules/@types/whatwg-url": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", - "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", - "license": "MIT", - "dependencies": { - "@types/webidl-conversions": "*" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", - "license": "MIT" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/bson": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", - "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.20.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/connect-flash": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", - "integrity": "sha512-2rcfELQt/ZMP+SM/pG8PyhJRaLKp+6Hk2IUBNkEit09X+vwn3QsAL3ZbYtxUn7NVPzbMTSLRDhqe0B/eh30RYA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", - "license": "MIT", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-ejs-layouts": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/express-ejs-layouts/-/express-ejs-layouts-2.5.1.tgz", - "integrity": "sha512-IXROv9n3xKga7FowT06n1Qn927JR8ZWDn5Dc9CJQoiiaaDqbhW5PDmWShzbpAa2wjWT1vJqaIM1S6vJwwX11gA==" - }, - "node_modules/express-session": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", - "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", - "license": "MIT", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/express-session/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express-session/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/express/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fast-printf": { - "version": "1.6.10", - "resolved": "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.10.tgz", - "integrity": "sha512-GwTgG9O4FVIdShhbVF3JxOgSBY2+ePGsu2V/UONgoCPzF9VY6ZdBMKsHKCYQHZwNk3qNouUolRDsgVxcVA5G1w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=10.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/i18n": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/i18n/-/i18n-0.15.1.tgz", - "integrity": "sha512-yue187t8MqUPMHdKjiZGrX+L+xcUsDClGO0Cz4loaKUOK9WrGw5pgan4bv130utOwX7fHE9w2iUeHFalVQWkXA==", - "license": "MIT", - "dependencies": { - "@messageformat/core": "^3.0.0", - "debug": "^4.3.3", - "fast-printf": "^1.6.9", - "make-plural": "^7.0.0", - "math-interval-parser": "^2.0.1", - "mustache": "^4.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/mashpie" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/kareem": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", - "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/make-plural": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.4.0.tgz", - "integrity": "sha512-4/gC9KVNTV6pvYg2gFeQYTW3mWaoJt7WZE5vrp1KnQDgW92JtYZnzmZT81oj/dUTqAIu0ufI2x3dkgu3bB1tYg==", - "license": "Unicode-DFS-2016" - }, - "node_modules/math-interval-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-2.0.1.tgz", - "integrity": "sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "license": "MIT" - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mongodb": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", - "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", - "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", - "license": "Apache-2.0", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^14.1.0 || ^13.0.0" - } - }, - "node_modules/mongoose": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz", - "integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==", - "license": "MIT", - "dependencies": { - "bson": "^6.10.4", - "kareem": "2.6.3", - "mongodb": "~6.17.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" - }, - "engines": { - "node": ">=16.20.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/moo": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", - "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", - "license": "BSD-3-Clause" - }, - "node_modules/mpath": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", - "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", - "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", - "license": "MIT", - "dependencies": { - "debug": "4.x" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/multer": { - "version": "1.4.5-lts.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", - "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", - "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", - "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "license": "MIT", - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/nodemon": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", - "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/passport": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", - "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", - "license": "MIT", - "dependencies": { - "passport-strategy": "1.x.x", - "pause": "0.0.1", - "utils-merge": "^1.0.1" - }, - "engines": { - "node": ">= 0.4.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jaredhanson" - } - }, - "node_modules/passport-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", - "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", - "dependencies": { - "passport-strategy": "1.x.x" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/passport-strategy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/pause": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-identifier": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz", - "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==", - "license": "ISC" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@img/colour": "^1.0.0", - "detect-libc": "^2.1.2", - "semver": "^7.7.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" - } - }, - "node_modules/sharp/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sift": { - "version": "17.1.3", - "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", - "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", - "license": "MIT" - }, - "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "~7.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/slugify": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", - "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "license": "MIT", - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "license": "MIT", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - } - } -}