diff --git a/.env.development b/.env.development
index 63d130b..232ed0c 100644
--- a/.env.development
+++ b/.env.development
@@ -40,4 +40,8 @@ SCRIPTS_DIR=admin_scripts
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]
DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7"
OLLAMA_URL=http://localhost:11434
-OLLAMA_DEFAULT_MODEL=llama3.2:3b
\ No newline at end of file
+OLLAMA_DEFAULT_MODEL=llama3.2:3b
+GROK_API="xai-PcNM5obgPaETtmnfDWPZk235D75ZgxENU2QmeqPfMQCHh9dwCDVeRRe0oVVA2YOpiUDh1uJieZsMasja"
+REPLICATE_API_TOKEN="r8_AVhM6igwvoOnUA65cHVZdhEDfTqBVk94WTB0u"
+FAL_KEY="7d251c88-21b5-4b55-8b3e-4bafd910f99f:b81c0a36a25b052f26eb8ac226c7efff"
+HF_TOKEN="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"
\ No newline at end of file
diff --git a/emails/RISO/circuit_abilitato_al_fido_membro/it/html.pug b/emails/RISO/circuit_abilitato_al_fido_membro/it/html.pug
index ebd5a66..7882a33 100755
--- a/emails/RISO/circuit_abilitato_al_fido_membro/it/html.pug
+++ b/emails/RISO/circuit_abilitato_al_fido_membro/it/html.pug
@@ -365,7 +365,7 @@ html(lang="it")
//- Intro
.intro-text
| Ciao #{usernameMembro},
- | complimenti! Sei stato abilitato al Circuito RIS del tuo territorio da #{usernameInvitante}.
+ | complimenti! Sei stato abilitato #{nomeTerritorio} da #{usernameInvitante}.
if linkProfiloAdmin
.divider(style="margin: 16px 0;")
@@ -379,7 +379,7 @@ html(lang="it")
.congrats-icon ✅
h3 Abilitazione Completata
p(style="font-size: 15px; color: #555; margin-top: 8px;")
- | Ora puoi utilizzare i RIS per i tuoi scambi nella comunità
+ | Ora puoi utilizzare i #{symbol} per i tuoi scambi nella comunità
.territory-name 📍 #{nomeTerritorio}
//- Info comunità
@@ -448,7 +448,7 @@ html(lang="it")
.step-number 1
.step-content
h5 Esplora la Piattaforma
- p Familiarizza con gli annunci, i membri e le funzionalità del Circuito RIS
+ p Familiarizza con gli annunci, i membri e le funzionalità del #{nomeTerritorio}
.step-item
.step-number 2
.step-content
diff --git a/emails/RISO/circuit_abilitato_al_fido_membro/it/subject.pug b/emails/RISO/circuit_abilitato_al_fido_membro/it/subject.pug
index 05304aa..de7bb00 100755
--- a/emails/RISO/circuit_abilitato_al_fido_membro/it/subject.pug
+++ b/emails/RISO/circuit_abilitato_al_fido_membro/it/subject.pug
@@ -1 +1 @@
-=`Richiesta ingresso di ${usernameMembro} - ${nomeMembro} ${cognomeMembro} su ${nomeTerritorio} in ${nomeapp}`
+=`Abilitazione avvenuta su ${nomeTerritorio} in ${nomeapp} - (${usernameMembro})`
diff --git a/emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/html.pug b/emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/html.pug
index 24367af..1204e11 100755
--- a/emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/html.pug
+++ b/emails/RISO/circuit_chiedi_facilitatori_di_entrare/it/html.pug
@@ -300,7 +300,7 @@ html(lang="it")
//- Intro
.intro-text
| Ciao #{nomeFacilitatore},
- | un nuovo membro richiede l'abilitazione alla fiducia al Circuito RIS del tuo territorio!
+ | un nuovo membro richiede l'abilitazione alla fiducia al Circuito del tuo territorio!
//- Card richiesta
.request-card
@@ -384,12 +384,12 @@ html(lang="it")
span.responsibility-icon 👥
span.responsibility-text
strong Integrazione:
- | Supporta il nuovo membro nell'attivazione e utilizzo del Circuito RIS locale
+ | Supporta il nuovo membro nell'attivazione e utilizzo del Circuito locale
//- Info box
.info-box
p
- | ✓ Dopo l'abilitazione, #{usernameMembro} potrà accedere al Circuito RIS di #{nomeTerritorio}
+ | ✓ Dopo l'abilitazione, #{usernameMembro} potrà accedere al #{nomeTerritorio}
p
| ✓ Il membro riceverà una notifica automatica dell'avvenuta attivazione
diff --git a/package.json b/package.json
index 0f7467b..332f786 100755
--- a/package.json
+++ b/package.json
@@ -16,11 +16,13 @@
"author": "Surya",
"license": "MIT",
"dependencies": {
+ "@fal-ai/client": "^1.7.2",
"axios": "^1.13.0",
"basic-ftp": "^5.0.5",
"bcryptjs": "^3.0.2",
"bluebird": "^3.7.2",
"body-parser": "^1.20.3",
+ "canvas": "^3.2.0",
"cheerio": "^1.0.0",
"compress-pdf": "^0.5.3",
"cookie-parser": "^1.4.7",
@@ -38,6 +40,7 @@
"formidable": "^3.5.2",
"fs-extra": "^11.3.2",
"ghostscript4js": "^3.2.3",
+ "groq-sdk": "^0.37.0",
"helmet": "^8.1.0",
"i18n": "^0.15.1",
"image-downloader": "^4.3.0",
@@ -59,7 +62,7 @@
"node-telegram-bot-api": "^0.66.0",
"nodemailer": "^6.10.0",
"npm-check-updates": "^17.1.15",
- "openai": "^4.86.2",
+ "openai": "^4.104.0",
"pdf-lib": "^1.17.1",
"pdf-parse": "^1.1.1",
"pem": "^1.14.8",
@@ -67,6 +70,7 @@
"pug": "^3.0.3",
"puppeteer": "^24.9.0",
"rate-limiter-flexible": "^5.0.5",
+ "replicate": "^1.4.0",
"request": "^2.88",
"sanitize-html": "^2.14.0",
"save": "^2.9.0",
diff --git a/src/controllers/assetController.js b/src/controllers/assetController.js
new file mode 100644
index 0000000..b2a632b
--- /dev/null
+++ b/src/controllers/assetController.js
@@ -0,0 +1,398 @@
+const Asset = require('../models/Asset');
+const imageGenerator = require('../services/imageGenerator');
+const sharp = require('sharp');
+const path = require('path');
+const fs = require('fs').promises;
+
+const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
+
+const assetController = {
+ // POST /assets/upload
+ async upload(req, res) {
+ try {
+ if (!req.file) {
+ return res.status(400).json({
+ success: false,
+ error: 'Nessun file caricato'
+ });
+ }
+
+ const { category = 'other', tags, description, isReusable = true } = req.body;
+ const file = req.file;
+
+ // Ottieni dimensioni immagine
+ let dimensions = {};
+ try {
+ const metadata = await sharp(file.path).metadata();
+ dimensions = { width: metadata.width, height: metadata.height };
+ } catch (e) {
+ console.warn('Cannot read image dimensions');
+ }
+
+ // Genera thumbnail
+ const thumbDir = path.join(UPLOAD_DIR, 'thumbs');
+ await fs.mkdir(thumbDir, { recursive: true });
+ const thumbName = `thumb_${file.filename}`;
+ const thumbPath = path.join(thumbDir, thumbName);
+
+ try {
+ await sharp(file.path)
+ .resize(300, 300, { fit: 'cover' })
+ .jpeg({ quality: 80 })
+ .toFile(thumbPath);
+ } catch (e) {
+ console.warn('Cannot create thumbnail');
+ }
+
+ const asset = new Asset({
+ type: 'image',
+ category,
+ sourceType: 'upload',
+ file: {
+ path: file.path,
+ url: `/uploads/${file.filename}`,
+ thumbnailPath: thumbPath,
+ thumbnailUrl: `/uploads/thumbs/${thumbName}`,
+ originalName: file.originalname,
+ mimeType: file.mimetype,
+ size: file.size,
+ dimensions
+ },
+ metadata: {
+ userId: req.user._id,
+ tags: tags ? tags.split(',').map(t => t.trim()) : [],
+ description,
+ isReusable: isReusable === 'true' || isReusable === true
+ }
+ });
+
+ await asset.save();
+
+ res.status(201).json({
+ success: true,
+ data: asset
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // POST /assets/upload-multiple
+ async uploadMultiple(req, res) {
+ try {
+ if (!req.files || req.files.length === 0) {
+ return res.status(400).json({
+ success: false,
+ error: 'Nessun file caricato'
+ });
+ }
+
+ const { category = 'other' } = req.body;
+ const assets = [];
+
+ for (const file of req.files) {
+ let dimensions = {};
+ try {
+ const metadata = await sharp(file.path).metadata();
+ dimensions = { width: metadata.width, height: metadata.height };
+ } catch (e) {}
+
+ const asset = new Asset({
+ type: 'image',
+ category,
+ sourceType: 'upload',
+ file: {
+ path: file.path,
+ url: `/uploads/${file.filename}`,
+ originalName: file.originalname,
+ mimeType: file.mimetype,
+ size: file.size,
+ dimensions
+ },
+ metadata: {
+ userId: req.user._id
+ }
+ });
+
+ await asset.save();
+ assets.push(asset);
+ }
+
+ res.status(201).json({
+ success: true,
+ data: assets
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // POST /assets/generate-ai
+ async generateAi(req, res) {
+ try {
+ const {
+ prompt,
+ negativePrompt,
+ provider = 'hf',
+ category = 'other',
+ aspectRatio = '9:16',
+ model,
+ seed,
+ steps,
+ cfg
+ } = req.body;
+
+ if (!prompt) {
+ return res.status(400).json({
+ success: false,
+ error: 'Prompt richiesto'
+ });
+ }
+
+ const startTime = Date.now();
+ const imageUrl = await imageGenerator.generate(provider, prompt, {
+ negativePrompt,
+ aspectRatio,
+ model,
+ seed,
+ steps,
+ cfg
+ });
+ const generationTime = Date.now() - startTime;
+
+ // Salva file
+ const fileName = `ai_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`;
+ const filePath = path.join(UPLOAD_DIR, 'ai-generated', fileName);
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
+
+ let fileSize = 0;
+ let dimensions = {};
+
+ if (imageUrl.startsWith('data:')) {
+ const base64Data = imageUrl.replace(/^data:image\/\w+;base64,/, '');
+ const buffer = Buffer.from(base64Data, 'base64');
+ await fs.writeFile(filePath, buffer);
+ fileSize = buffer.length;
+
+ const metadata = await sharp(buffer).metadata();
+ dimensions = { width: metadata.width, height: metadata.height };
+ } else {
+ const fetch = require('node-fetch');
+ const response = await fetch(imageUrl);
+ const buffer = await response.buffer();
+ await fs.writeFile(filePath, buffer);
+ fileSize = buffer.length;
+
+ const metadata = await sharp(buffer).metadata();
+ dimensions = { width: metadata.width, height: metadata.height };
+ }
+
+ const asset = new Asset({
+ type: 'image',
+ category,
+ sourceType: 'ai',
+ file: {
+ path: filePath,
+ url: `/uploads/ai-generated/${fileName}`,
+ mimeType: 'image/jpeg',
+ size: fileSize,
+ dimensions
+ },
+ aiGeneration: {
+ prompt,
+ negativePrompt,
+ provider,
+ model,
+ seed,
+ steps,
+ cfg,
+ requestedSize: aspectRatio,
+ generationTime
+ },
+ metadata: {
+ userId: req.user._id,
+ isReusable: true
+ }
+ });
+
+ await asset.save();
+
+ res.status(201).json({
+ success: true,
+ data: asset
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /assets
+ async list(req, res) {
+ try {
+ const {
+ category,
+ sourceType,
+ page = 1,
+ limit = 50
+ } = req.query;
+
+ const query = {
+ 'metadata.userId': req.user._id,
+ status: 'ready'
+ };
+
+ if (category) query.category = category;
+ if (sourceType) query.sourceType = sourceType;
+
+ const [assets, total] = await Promise.all([
+ Asset.find(query)
+ .sort({ createdAt: -1 })
+ .skip((page - 1) * limit)
+ .limit(parseInt(limit)),
+ Asset.countDocuments(query)
+ ]);
+
+ res.json({
+ success: true,
+ data: assets,
+ pagination: {
+ page: parseInt(page),
+ limit: parseInt(limit),
+ total,
+ pages: Math.ceil(total / limit)
+ }
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /assets/:id
+ async getById(req, res) {
+ try {
+ const asset = await Asset.findById(req.params.id);
+
+ if (!asset) {
+ return res.status(404).json({
+ success: false,
+ error: 'Asset non trovato'
+ });
+ }
+
+ res.json({
+ success: true,
+ data: asset
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /assets/:id/file
+ async getFile(req, res) {
+ try {
+ const asset = await Asset.findById(req.params.id);
+
+ if (!asset || !asset.file?.path) {
+ return res.status(404).json({
+ success: false,
+ error: 'File non trovato'
+ });
+ }
+
+ res.sendFile(path.resolve(asset.file.path));
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /assets/:id/thumbnail
+ async getThumbnail(req, res) {
+ try {
+ const asset = await Asset.findById(req.params.id);
+
+ if (!asset) {
+ return res.status(404).json({
+ success: false,
+ error: 'Asset non trovato'
+ });
+ }
+
+ const thumbPath = asset.file?.thumbnailPath || asset.file?.path;
+ if (!thumbPath) {
+ return res.status(404).json({
+ success: false,
+ error: 'Thumbnail non disponibile'
+ });
+ }
+
+ res.sendFile(path.resolve(thumbPath));
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // DELETE /assets/:id
+ async delete(req, res) {
+ try {
+ const asset = await Asset.findById(req.params.id);
+
+ if (!asset) {
+ return res.status(404).json({
+ success: false,
+ error: 'Asset non trovato'
+ });
+ }
+
+ if (asset.metadata.userId.toString() !== req.user._id.toString()) {
+ return res.status(403).json({
+ success: false,
+ error: 'Non autorizzato'
+ });
+ }
+
+ // Elimina file
+ try {
+ if (asset.file?.path) await fs.unlink(asset.file.path);
+ if (asset.file?.thumbnailPath) await fs.unlink(asset.file.thumbnailPath);
+ } catch (e) {
+ console.warn('File deletion warning:', e.message);
+ }
+
+ await Asset.deleteOne({ _id: asset._id });
+
+ res.json({
+ success: true,
+ message: 'Asset eliminato'
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ }
+};
+
+module.exports = assetController;
\ No newline at end of file
diff --git a/src/controllers/posterController.js b/src/controllers/posterController.js
new file mode 100644
index 0000000..29e5873
--- /dev/null
+++ b/src/controllers/posterController.js
@@ -0,0 +1,647 @@
+const Poster = require('../models/Poster');
+const Template = require('../models/Template');
+const Asset = require('../models/Asset');
+const posterRenderer = require('../services/posterRenderer');
+const imageGenerator = require('../services/imageGenerator');
+const path = require('path');
+const fs = require('fs').promises;
+
+const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
+
+const posterController = {
+ // POST /posters
+ async create(req, res) {
+ try {
+ const {
+ templateId,
+ name,
+ description,
+ content,
+ assets,
+ layerOverrides,
+ autoRender = false
+ } = req.body;
+
+ // Carica template
+ const template = await Template.findById(templateId);
+ if (!template) {
+ return res.status(404).json({
+ success: false,
+ error: 'Template non trovato'
+ });
+ }
+
+ // Valida contenuti richiesti
+ const requiredLayers = template.layers.filter(l => l.required);
+ for (const layer of requiredLayers) {
+ if (layer.type === 'title' && !content?.title) {
+ return res.status(400).json({
+ success: false,
+ error: `Campo richiesto: ${layer.type}`
+ });
+ }
+ }
+
+ const poster = new Poster({
+ templateId,
+ templateSnapshot: template.toObject(), // Snapshot per retrocompatibilità
+ name: name || content?.title || 'Nuova Locandina',
+ description,
+ status: 'draft',
+ content: content || {},
+ assets: assets || {},
+ layerOverrides: layerOverrides || {},
+ renderEngineVersion: '1.0.0',
+ metadata: {
+ userId: req.user._id
+ }
+ });
+
+ await poster.save();
+
+ // Incrementa uso template
+ await template.incrementUsage();
+
+ // Auto-render se richiesto
+ if (autoRender) {
+ await posterController._renderPoster(poster);
+ await poster.save();
+ }
+
+ res.status(201).json({
+ success: true,
+ data: poster
+ });
+ } catch (error) {
+ console.error('Poster create error:', error);
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /posters
+ async list(req, res) {
+ try {
+ const {
+ status,
+ templateId,
+ search,
+ page = 1,
+ limit = 20,
+ sortBy = 'createdAt',
+ sortOrder = 'desc'
+ } = req.query;
+
+ const query = { 'metadata.userId': req.user._id };
+
+ if (status) query.status = status;
+ if (templateId) query.templateId = templateId;
+ if (search) query.$text = { $search: search };
+
+ const sort = { [sortBy]: sortOrder === 'asc' ? 1 : -1 };
+
+ const [posters, total] = await Promise.all([
+ Poster.find(query)
+ .populate('templateId', 'name templateType thumbnailUrl')
+ .sort(sort)
+ .skip((page - 1) * limit)
+ .limit(parseInt(limit))
+ .select('-templateSnapshot -history'),
+ Poster.countDocuments(query)
+ ]);
+
+ res.json({
+ success: true,
+ data: posters,
+ pagination: {
+ page: parseInt(page),
+ limit: parseInt(limit),
+ total,
+ pages: Math.ceil(total / limit)
+ }
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /posters/favorites
+ async listFavorites(req, res) {
+ try {
+ const posters = await Poster.findFavorites(req.user._id);
+
+ res.json({
+ success: true,
+ data: posters
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /posters/recent
+ async listRecent(req, res) {
+ try {
+ const { limit = 10 } = req.query;
+ const posters = await Poster.findRecent(req.user._id, parseInt(limit));
+
+ res.json({
+ success: true,
+ data: posters
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /posters/:id
+ async getById(req, res) {
+ try {
+ const poster = await Poster.findById(req.params.id)
+ .populate('templateId')
+ .populate('assets.backgroundImage.assetId')
+ .populate('assets.mainImage.assetId')
+ .populate('assets.logos.assetId');
+
+ if (!poster) {
+ return res.status(404).json({
+ success: false,
+ error: 'Poster non trovato'
+ });
+ }
+
+ // Check ownership
+ if (poster.metadata.userId.toString() !== req.user._id.toString()) {
+ return res.status(403).json({
+ success: false,
+ error: 'Accesso negato'
+ });
+ }
+
+ res.json({
+ success: true,
+ data: poster
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // PUT /posters/:id
+ async update(req, res) {
+ try {
+ const poster = await Poster.findById(req.params.id);
+
+ if (!poster) {
+ return res.status(404).json({
+ success: false,
+ error: 'Poster non trovato'
+ });
+ }
+
+ if (poster.metadata.userId.toString() !== req.user._id.toString()) {
+ return res.status(403).json({
+ success: false,
+ error: 'Non autorizzato'
+ });
+ }
+
+ const updateFields = [
+ 'name', 'description', 'content', 'assets', 'layerOverrides'
+ ];
+
+ updateFields.forEach(field => {
+ if (req.body[field] !== undefined) {
+ poster[field] = req.body[field];
+ }
+ });
+
+ // Invalida render precedente se contenuto modificato
+ if (req.body.content || req.body.assets || req.body.layerOverrides) {
+ poster.status = 'draft';
+ poster.addHistory('updated', { fields: Object.keys(req.body) });
+ }
+
+ await poster.save();
+
+ res.json({
+ success: true,
+ data: poster
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // DELETE /posters/:id
+ async delete(req, res) {
+ try {
+ const poster = await Poster.findById(req.params.id);
+
+ if (!poster) {
+ return res.status(404).json({
+ success: false,
+ error: 'Poster non trovato'
+ });
+ }
+
+ if (poster.metadata.userId.toString() !== req.user._id.toString()) {
+ return res.status(403).json({
+ success: false,
+ error: 'Non autorizzato'
+ });
+ }
+
+ // Elimina file renderizzati
+ if (poster.renderOutput) {
+ const filesToDelete = [
+ poster.renderOutput.png?.path,
+ poster.renderOutput.jpg?.path,
+ poster.renderOutput.webp?.path
+ ].filter(Boolean);
+
+ for (const filePath of filesToDelete) {
+ try {
+ await fs.unlink(filePath);
+ } catch (e) {
+ console.warn('File not found:', filePath);
+ }
+ }
+ }
+
+ await Poster.deleteOne({ _id: poster._id });
+
+ res.json({
+ success: true,
+ message: 'Poster eliminato'
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // POST /posters/:id/render
+ async render(req, res) {
+ try {
+ const poster = await Poster.findById(req.params.id)
+ .populate('templateId');
+
+ if (!poster) {
+ return res.status(404).json({
+ success: false,
+ error: 'Poster non trovato'
+ });
+ }
+
+ if (poster.metadata.userId.toString() !== req.user._id.toString()) {
+ return res.status(403).json({
+ success: false,
+ error: 'Non autorizzato'
+ });
+ }
+
+ poster.status = 'processing';
+ await poster.save();
+
+ try {
+ await posterController._renderPoster(poster);
+ await poster.save();
+
+ res.json({
+ success: true,
+ data: {
+ status: poster.status,
+ renderOutput: poster.renderOutput
+ }
+ });
+ } catch (renderError) {
+ poster.setError(renderError.message);
+ await poster.save();
+ throw renderError;
+ }
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // POST /posters/:id/regenerate-ai
+ async regenerateAi(req, res) {
+ try {
+ const { assetType, prompt, provider = 'hf' } = req.body;
+ const poster = await Poster.findById(req.params.id);
+
+ if (!poster) {
+ return res.status(404).json({
+ success: false,
+ error: 'Poster non trovato'
+ });
+ }
+
+ if (poster.metadata.userId.toString() !== req.user._id.toString()) {
+ return res.status(403).json({
+ success: false,
+ error: 'Non autorizzato'
+ });
+ }
+
+ // Genera nuova immagine AI
+ const startTime = Date.now();
+ const imageUrl = await imageGenerator.generate(provider, prompt);
+ const generationTime = Date.now() - startTime;
+
+ // Salva su filesystem
+ const fileName = `${poster._id}_${assetType}_${Date.now()}.jpg`;
+ const filePath = path.join(UPLOAD_DIR, 'ai-generated', fileName);
+
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
+
+ // Se è base64, converti
+ let savedPath;
+ if (imageUrl.startsWith('data:')) {
+ const base64Data = imageUrl.replace(/^data:image\/\w+;base64,/, '');
+ await fs.writeFile(filePath, base64Data, 'base64');
+ savedPath = filePath;
+ } else {
+ // Se è URL, scarica
+ const fetch = require('node-fetch');
+ const response = await fetch(imageUrl);
+ const buffer = await response.buffer();
+ await fs.writeFile(filePath, buffer);
+ savedPath = filePath;
+ }
+
+ // Aggiorna asset nel poster
+ const assetData = {
+ sourceType: 'ai',
+ url: `/uploads/ai-generated/${fileName}`,
+ mimeType: 'image/jpeg',
+ aiParams: {
+ prompt,
+ provider,
+ generatedAt: new Date()
+ }
+ };
+
+ if (assetType === 'backgroundImage') {
+ poster.assets.backgroundImage = assetData;
+ poster.addHistory('ai_background_generated', { provider, duration: generationTime });
+ } else if (assetType === 'mainImage') {
+ poster.assets.mainImage = assetData;
+ poster.addHistory('ai_main_generated', { provider, duration: generationTime });
+ }
+
+ poster.status = 'draft';
+ await poster.save();
+
+ res.json({
+ success: true,
+ data: {
+ assetType,
+ asset: assetData
+ }
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /posters/:id/download/:format
+ async download(req, res) {
+ try {
+ const { format } = req.params;
+ const poster = await Poster.findById(req.params.id);
+
+ if (!poster) {
+ return res.status(404).json({
+ success: false,
+ error: 'Poster non trovato'
+ });
+ }
+
+ const outputFile = poster.renderOutput?.[format];
+ if (!outputFile?.path) {
+ return res.status(404).json({
+ success: false,
+ error: `Formato ${format} non disponibile`
+ });
+ }
+
+ // Incrementa download count
+ await poster.incrementDownload();
+
+ const fileName = `${poster.name.replace(/[^a-z0-9]/gi, '_')}_poster.${format}`;
+
+ res.download(outputFile.path, fileName);
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // POST /posters/:id/favorite
+ async toggleFavorite(req, res) {
+ try {
+ const poster = await Poster.findById(req.params.id);
+
+ if (!poster) {
+ return res.status(404).json({
+ success: false,
+ error: 'Poster non trovato'
+ });
+ }
+
+ if (poster.metadata.userId.toString() !== req.user._id.toString()) {
+ return res.status(403).json({
+ success: false,
+ error: 'Non autorizzato'
+ });
+ }
+
+ await poster.toggleFavorite();
+
+ res.json({
+ success: true,
+ data: {
+ isFavorite: poster.metadata.isFavorite
+ }
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // POST /posters/quick-generate (compatibile con la tua bozza)
+ async quickGenerate(req, res) {
+ try {
+ const {
+ templateId,
+ titolo,
+ descrizione,
+ data,
+ ora,
+ luogo,
+ contatti,
+ fotoDescrizione,
+ stile,
+ provider = 'hf',
+ aspectRatio = '9:16'
+ } = req.body;
+
+ // Validazione base
+ if (!titolo || !data || !luogo) {
+ return res.status(400).json({
+ success: false,
+ error: 'Compila titolo, data e luogo'
+ });
+ }
+
+ // Usa template default o quello specificato
+ let template;
+ if (templateId) {
+ template = await Template.findById(templateId);
+ } else {
+ // Template default per quick-generate
+ template = await Template.findOne({
+ templateType: 'quick-generate',
+ isActive: true
+ });
+ }
+
+ // Genera prompt per AI background
+ const aiPrompt = `Vertical event poster background, ${stile || 'modern style, vivid colors'}. Subject: ${fotoDescrizione || 'abstract artistic shapes'}. Composition: Central empty space suitable for text overlay. NO TEXT, NO LETTERS, clean illustration, high quality, 4k.`;
+
+ // Genera immagine AI
+ const startTime = Date.now();
+ const rawImageUrl = await imageGenerator.generate(provider, aiPrompt);
+ const generationTime = Date.now() - startTime;
+
+ // Salva asset generato
+ const fileName = `quick_${Date.now()}.jpg`;
+ const filePath = path.join(UPLOAD_DIR, 'ai-generated', fileName);
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
+
+ if (rawImageUrl.startsWith('data:')) {
+ const base64Data = rawImageUrl.replace(/^data:image\/\w+;base64,/, '');
+ await fs.writeFile(filePath, base64Data, 'base64');
+ }
+
+ // Crea poster
+ const poster = new Poster({
+ templateId: template?._id,
+ name: titolo,
+ status: 'processing',
+ content: {
+ title: titolo,
+ subtitle: descrizione,
+ eventDate: data,
+ eventTime: ora,
+ location: luogo,
+ contacts: contatti
+ },
+ assets: {
+ backgroundImage: {
+ sourceType: 'ai',
+ url: `/uploads/ai-generated/${fileName}`,
+ mimeType: 'image/jpeg',
+ aiParams: {
+ prompt: aiPrompt,
+ provider,
+ generatedAt: new Date()
+ }
+ }
+ },
+ originalPrompt: aiPrompt,
+ styleUsed: stile,
+ aspectRatio,
+ provider,
+ metadata: {
+ userId: req.user._id
+ }
+ });
+
+ poster.addHistory('ai_background_generated', { provider, duration: generationTime });
+
+ // Render con testi sovrapposti
+ await posterController._renderPoster(poster, { useQuickRender: true });
+ await poster.save();
+
+ res.json({
+ success: true,
+ data: {
+ posterId: poster._id,
+ imageUrl: poster.renderOutput?.png?.url || rawImageUrl,
+ status: poster.status
+ }
+ });
+ } catch (error) {
+ console.error('Quick generate error:', error);
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // Helper interno: renderizza poster
+ async _renderPoster(poster, options = {}) {
+ const template = poster.templateId || poster.templateSnapshot;
+
+ const result = await posterRenderer.render({
+ template,
+ content: poster.content,
+ assets: poster.assets,
+ layerOverrides: Object.fromEntries(poster.layerOverrides || new Map()),
+ outputDir: path.join(UPLOAD_DIR, 'posters', 'final'),
+ posterId: poster._id.toString()
+ });
+
+ poster.setRenderOutput({
+ png: {
+ path: result.pngPath,
+ url: `/uploads/posters/final/${path.basename(result.pngPath)}`,
+ size: result.pngSize
+ },
+ jpg: {
+ path: result.jpgPath,
+ url: `/uploads/posters/final/${path.basename(result.jpgPath)}`,
+ size: result.jpgSize,
+ quality: 95
+ },
+ dimensions: result.dimensions,
+ duration: result.duration
+ });
+ }
+};
+
+module.exports = posterController;
\ No newline at end of file
diff --git a/src/controllers/templateController.js b/src/controllers/templateController.js
new file mode 100644
index 0000000..41cc92a
--- /dev/null
+++ b/src/controllers/templateController.js
@@ -0,0 +1,383 @@
+const Template = require('../models/Template');
+
+// Presets formati standard
+const FORMAT_PRESETS = {
+ 'A4': { width: 2480, height: 3508, dpi: 300 },
+ 'A4-landscape': { width: 3508, height: 2480, dpi: 300 },
+ 'A3': { width: 3508, height: 4961, dpi: 300 },
+ 'A3-landscape': { width: 4961, height: 3508, dpi: 300 },
+ 'instagram-post': { width: 1080, height: 1080, dpi: 72 },
+ 'instagram-story': { width: 1080, height: 1920, dpi: 72 },
+ 'instagram-portrait': { width: 1080, height: 1350, dpi: 72 },
+ 'facebook-post': { width: 1200, height: 630, dpi: 72 },
+ 'facebook-event': { width: 1920, height: 1080, dpi: 72 },
+ 'twitter-post': { width: 1200, height: 675, dpi: 72 },
+ 'poster-24x36': { width: 7200, height: 10800, dpi: 300 },
+ 'flyer-5x7': { width: 1500, height: 2100, dpi: 300 }
+};
+
+const templateController = {
+ // POST /templates
+ async create(req, res) {
+ try {
+ const {
+ name,
+ templateType,
+ description,
+ format,
+ safeArea,
+ backgroundColor,
+ layers,
+ logoSlots,
+ palette,
+ typography,
+ defaultAiPromptHints,
+ metadata
+ } = req.body;
+
+ // Applica preset se specificato
+ let finalFormat = format;
+ if (format?.preset && FORMAT_PRESETS[format.preset]) {
+ finalFormat = {
+ ...FORMAT_PRESETS[format.preset],
+ preset: format.preset,
+ unit: 'px'
+ };
+ }
+
+ // Valida layers
+ if (!layers || !Array.isArray(layers) || layers.length === 0) {
+ return res.status(400).json({
+ success: false,
+ error: 'Almeno un layer è richiesto'
+ });
+ }
+
+ // Assicura ID unici per layer
+ const layersWithIds = layers.map((layer, idx) => ({
+ ...layer,
+ id: layer.id || `layer_${layer.type}_${idx}`
+ }));
+
+ const template = new Template({
+ name,
+ templateType,
+ description,
+ format: finalFormat,
+ safeArea: safeArea || {},
+ backgroundColor: backgroundColor || '#1a1a2e',
+ layers: layersWithIds,
+ logoSlots: logoSlots || { enabled: false, slots: [] },
+ palette: palette || {},
+ typography: typography || {},
+ defaultAiPromptHints: defaultAiPromptHints || {},
+ metadata: {
+ ...metadata,
+ author: req.user?.name || 'System'
+ },
+ userId: req.user?._id
+ });
+
+ await template.save();
+
+ res.status(201).json({
+ success: true,
+ data: template
+ });
+ } catch (error) {
+ console.error('Template create error:', error);
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /templates
+ async list(req, res) {
+ try {
+ const {
+ type,
+ search,
+ tags,
+ page = 1,
+ limit = 20,
+ sortBy = 'createdAt',
+ sortOrder = 'desc'
+ } = req.query;
+
+ const query = { isActive: true };
+
+ if (type) {
+ query.templateType = type;
+ }
+
+ if (tags) {
+ const tagArray = tags.split(',').map(t => t.trim());
+ query['metadata.tags'] = { $in: tagArray };
+ }
+
+ if (search) {
+ query.$text = { $search: search };
+ }
+
+ // Se utente autenticato, mostra anche i suoi privati
+ if (req.user) {
+ query.$or = [
+ { 'metadata.isPublic': true },
+ { userId: req.user._id }
+ ];
+ } else {
+ query['metadata.isPublic'] = true;
+ }
+
+ const sort = { [sortBy]: sortOrder === 'asc' ? 1 : -1 };
+
+ const [templates, total] = await Promise.all([
+ Template.find(query)
+ .sort(sort)
+ .skip((page - 1) * limit)
+ .limit(parseInt(limit))
+ .select('-layers -logoSlots'), // Escludi dati pesanti per list
+ Template.countDocuments(query)
+ ]);
+
+ res.json({
+ success: true,
+ data: templates,
+ pagination: {
+ page: parseInt(page),
+ limit: parseInt(limit),
+ total,
+ pages: Math.ceil(total / limit)
+ }
+ });
+ } catch (error) {
+ console.error('Template list error:', error);
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /templates/types
+ async getTypes(req, res) {
+ try {
+ const types = await Template.distinct('templateType', { isActive: true });
+
+ res.json({
+ success: true,
+ data: types.sort()
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /templates/presets
+ async getFormatPresets(req, res) {
+ res.json({
+ success: true,
+ data: FORMAT_PRESETS
+ });
+ },
+
+ // GET /templates/:id
+ async getById(req, res) {
+ try {
+ const template = await Template.findById(req.params.id);
+
+ if (!template) {
+ return res.status(404).json({
+ success: false,
+ error: 'Template non trovato'
+ });
+ }
+
+ // Check accesso
+ if (!template.metadata.isPublic &&
+ (!req.user || template.userId?.toString() !== req.user._id.toString())) {
+ return res.status(403).json({
+ success: false,
+ error: 'Accesso negato'
+ });
+ }
+
+ res.json({
+ success: true,
+ data: template
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // PUT /templates/:id
+ async update(req, res) {
+ try {
+ const template = await Template.findById(req.params.id);
+
+ if (!template) {
+ return res.status(404).json({
+ success: false,
+ error: 'Template non trovato'
+ });
+ }
+
+ // Check ownership
+ if (template.userId?.toString() !== req.user._id.toString()) {
+ return res.status(403).json({
+ success: false,
+ error: 'Non autorizzato a modificare questo template'
+ });
+ }
+
+ const updateFields = [
+ 'name', 'description', 'templateType', 'format', 'safeArea',
+ 'backgroundColor', 'layers', 'logoSlots', 'palette',
+ 'typography', 'defaultAiPromptHints', 'metadata', 'isActive'
+ ];
+
+ updateFields.forEach(field => {
+ if (req.body[field] !== undefined) {
+ template[field] = req.body[field];
+ }
+ });
+
+ // Incrementa versione
+ if (template.metadata) {
+ const version = template.metadata.version || '1.0.0';
+ const parts = version.split('.').map(Number);
+ parts[2]++;
+ template.metadata.version = parts.join('.');
+ }
+
+ await template.save();
+
+ res.json({
+ success: true,
+ data: template
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // DELETE /templates/:id
+ async delete(req, res) {
+ try {
+ const template = await Template.findById(req.params.id);
+
+ if (!template) {
+ return res.status(404).json({
+ success: false,
+ error: 'Template non trovato'
+ });
+ }
+
+ if (template.userId?.toString() !== req.user._id.toString()) {
+ return res.status(403).json({
+ success: false,
+ error: 'Non autorizzato'
+ });
+ }
+
+ // Soft delete
+ template.isActive = false;
+ await template.save();
+
+ res.json({
+ success: true,
+ message: 'Template eliminato'
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // POST /templates/:id/duplicate
+ async duplicate(req, res) {
+ try {
+ const original = await Template.findById(req.params.id);
+
+ if (!original) {
+ return res.status(404).json({
+ success: false,
+ error: 'Template non trovato'
+ });
+ }
+
+ const duplicateData = original.toObject();
+ delete duplicateData._id;
+ delete duplicateData.createdAt;
+ delete duplicateData.updatedAt;
+
+ duplicateData.name = `${original.name} (copia)`;
+ duplicateData.userId = req.user._id;
+ duplicateData.metadata = {
+ ...duplicateData.metadata,
+ isPublic: false,
+ usageCount: 0,
+ author: req.user?.name || 'System',
+ version: '1.0.0'
+ };
+
+ const duplicate = new Template(duplicateData);
+ await duplicate.save();
+
+ res.status(201).json({
+ success: true,
+ data: duplicate
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ },
+
+ // GET /templates/:id/preview
+ async getPreview(req, res) {
+ try {
+ const template = await Template.findById(req.params.id)
+ .select('previewUrl thumbnailUrl name');
+
+ if (!template) {
+ return res.status(404).json({
+ success: false,
+ error: 'Template non trovato'
+ });
+ }
+
+ res.json({
+ success: true,
+ data: {
+ previewUrl: template.previewUrl,
+ thumbnailUrl: template.thumbnailUrl,
+ name: template.name
+ }
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+ }
+};
+
+module.exports = templateController;
\ No newline at end of file
diff --git a/src/data/asset.json b/src/data/asset.json
new file mode 100644
index 0000000..8389cca
--- /dev/null
+++ b/src/data/asset.json
@@ -0,0 +1,45 @@
+{
+ "_id": "asset_bg_001",
+ "type": "image",
+ "category": "background",
+ "sourceType": "ai",
+
+ "file": {
+ "path": "/uploads/assets/backgrounds/forest_autumn_001.jpg",
+ "url": "/api/assets/asset_bg_001/file",
+ "thumbnailPath": "/uploads/assets/backgrounds/thumbs/forest_autumn_001_thumb.jpg",
+ "thumbnailUrl": "/api/assets/asset_bg_001/thumbnail",
+ "originalName": null,
+ "mimeType": "image/jpeg",
+ "size": 2458000,
+ "dimensions": { "width": 2480, "height": 3508 }
+ },
+
+ "aiGeneration": {
+ "prompt": "Mystical autumn forest at golden hour...",
+ "negativePrompt": "text, letters, words...",
+ "provider": "hf",
+ "model": "FLUX.1-dev",
+ "seed": 8847291,
+ "steps": 35,
+ "cfg": 7.5,
+ "requestedSize": "1024x1536",
+ "actualSize": "1024x1536",
+ "generationTime": 12500,
+ "cost": 0
+ },
+
+ "usage": {
+ "usedInPosters": ["poster_sagra_funghi_2025_001"],
+ "usedInTemplates": [],
+ "usageCount": 1
+ },
+
+ "metadata": {
+ "userId": "user_001",
+ "tags": ["forest", "autumn", "background", "nature"],
+ "isReusable": true
+ },
+
+ "createdAt": "2025-01-15T10:25:00.000Z"
+}
\ No newline at end of file
diff --git a/src/data/poster.json b/src/data/poster.json
new file mode 100644
index 0000000..21294d2
--- /dev/null
+++ b/src/data/poster.json
@@ -0,0 +1,150 @@
+{
+ "_id": "poster_sagra_funghi_2025_001",
+ "templateId": "template_raccolta_funghi_001",
+ "name": "Sagra del Fungo Porcino 2025",
+ "status": "completed",
+
+ "content": {
+ "title": "SAGRA DEL FUNGO PORCINO",
+ "subtitle": "XXV Edizione - Tradizione e Sapori del Bosco",
+ "eventDate": "15-16-17 Ottobre 2025",
+ "eventTime": "10:00 - 23:00",
+ "location": "Parco delle Querce, Borgo Montano (PG)",
+ "contacts": "Tel: 0742 123456 | info@sagrafungoporcino.it | www.sagrafungoporcino.it",
+ "extraText": [
+ "Ingresso Libero",
+ "Stand Gastronomici • Musica dal Vivo • Mercatino Artigianale"
+ ]
+ },
+
+ "assets": {
+ "backgroundImage": {
+ "id": "asset_bg_001",
+ "sourceType": "ai",
+ "url": "/uploads/posters/poster_sagra_2025_bg.jpg",
+ "thumbnailUrl": "/uploads/posters/thumbs/poster_sagra_2025_bg_thumb.jpg",
+ "mimeType": "image/jpeg",
+ "size": 2458000,
+ "dimensions": { "width": 2480, "height": 3508 },
+ "aiParams": {
+ "prompt": "Mystical autumn forest at golden hour, morning mist between ancient oak trees, forest floor covered with porcini mushrooms, warm orange and golden light filtering through leaves, photorealistic, cinematic composition, National Geographic style, 8k quality",
+ "negativePrompt": "text, letters, words, watermark, signature, blurry, low quality, cartoon, anime",
+ "provider": "hf",
+ "model": "FLUX.1-dev",
+ "seed": 8847291,
+ "steps": 35,
+ "cfg": 7.5,
+ "size": "1024x1536",
+ "generatedAt": "2025-01-15T10:25:00.000Z"
+ }
+ },
+ "mainImage": {
+ "id": "asset_main_001",
+ "sourceType": "upload",
+ "url": "/uploads/assets/porcini_basket_hero.jpg",
+ "thumbnailUrl": "/uploads/assets/thumbs/porcini_basket_hero_thumb.jpg",
+ "originalName": "IMG_20241015_porcini.jpg",
+ "mimeType": "image/jpeg",
+ "size": 1845000,
+ "dimensions": { "width": 1920, "height": 1280 },
+ "uploadedAt": "2025-01-15T10:20:00.000Z"
+ },
+ "logos": [
+ {
+ "id": "asset_logo_001",
+ "slotId": "logo_slot_1",
+ "sourceType": "upload",
+ "url": "/uploads/logos/comune_borgomontano.png",
+ "originalName": "logo_comune.png",
+ "mimeType": "image/png",
+ "size": 45000
+ },
+ {
+ "id": "asset_logo_002",
+ "slotId": "logo_slot_2",
+ "sourceType": "upload",
+ "url": "/uploads/logos/proloco_borgomontano.png",
+ "originalName": "logo_proloco.png",
+ "mimeType": "image/png",
+ "size": 38000
+ },
+ {
+ "id": "asset_logo_003",
+ "slotId": "logo_slot_3",
+ "sourceType": "ai",
+ "url": "/uploads/logos/ai_generated_mushroom_logo.png",
+ "mimeType": "image/png",
+ "size": 52000,
+ "aiParams": {
+ "prompt": "Minimal vector logo of a porcini mushroom, flat design, golden brown color, white background, simple elegant icon",
+ "provider": "ideogram",
+ "model": "ideogram-v2"
+ }
+ }
+ ]
+ },
+
+ "layerOverrides": {
+ "layer_title": {
+ "style": {
+ "fontSize": 78,
+ "color": "#fff8e7"
+ }
+ },
+ "layer_event_date": {
+ "style": {
+ "color": "#ffa502"
+ }
+ }
+ },
+
+ "renderOutput": {
+ "png": {
+ "path": "/uploads/posters/final/poster_sagra_2025_final.png",
+ "size": 8945000,
+ "url": "/api/posters/poster_sagra_funghi_2025_001/download/png"
+ },
+ "jpg": {
+ "path": "/uploads/posters/final/poster_sagra_2025_final.jpg",
+ "quality": 95,
+ "size": 2145000,
+ "url": "/api/posters/poster_sagra_funghi_2025_001/download/jpg"
+ },
+ "dimensions": {
+ "width": 2480,
+ "height": 3508
+ },
+ "renderedAt": "2025-01-15T10:30:00.000Z"
+ },
+
+ "renderEngineVersion": "1.0.0",
+
+ "history": [
+ {
+ "action": "created",
+ "timestamp": "2025-01-15T10:15:00.000Z",
+ "userId": "user_001"
+ },
+ {
+ "action": "ai_background_generated",
+ "timestamp": "2025-01-15T10:25:00.000Z",
+ "details": { "provider": "hf", "duration": 12500 }
+ },
+ {
+ "action": "rendered",
+ "timestamp": "2025-01-15T10:30:00.000Z",
+ "details": { "duration": 3200 }
+ }
+ ],
+
+ "metadata": {
+ "userId": "user_001",
+ "projectId": "project_eventi_2025",
+ "tags": ["sagra", "fungo", "autunno", "2025"],
+ "isPublic": false,
+ "isFavorite": true
+ },
+
+ "createdAt": "2025-01-15T10:15:00.000Z",
+ "updatedAt": "2025-01-15T10:30:00.000Z"
+}
\ No newline at end of file
diff --git a/src/data/template.json b/src/data/template.json
new file mode 100644
index 0000000..787cf8d
--- /dev/null
+++ b/src/data/template.json
@@ -0,0 +1,272 @@
+{
+ "_id": "template_raccolta_funghi_001",
+ "name": "Raccolta Funghi Autunnale",
+ "templateType": "outdoor-event",
+ "description": "Template per eventi all'aperto legati alla natura",
+
+ "format": {
+ "preset": "A4",
+ "width": 2480,
+ "height": 3508,
+ "unit": "px",
+ "dpi": 300
+ },
+
+ "safeArea": {
+ "top": 0.04,
+ "right": 0.04,
+ "bottom": 0.04,
+ "left": 0.04
+ },
+
+ "backgroundColor": "#1a1a2e",
+
+ "layers": [
+ {
+ "id": "layer_bg",
+ "type": "backgroundImage",
+ "zIndex": 0,
+ "position": { "x": 0, "y": 0, "w": 1, "h": 1 },
+ "anchor": "top-left",
+ "required": false,
+ "fallback": {
+ "type": "gradient",
+ "direction": "to-bottom",
+ "colors": ["#2d3436", "#636e72"]
+ },
+ "style": {
+ "opacity": 1,
+ "blur": 0,
+ "objectFit": "cover",
+ "overlay": {
+ "enabled": true,
+ "type": "gradient",
+ "direction": "to-bottom",
+ "stops": [
+ { "position": 0, "color": "rgba(0,0,0,0)" },
+ { "position": 0.5, "color": "rgba(0,0,0,0.3)" },
+ { "position": 1, "color": "rgba(0,0,0,0.85)" }
+ ]
+ }
+ }
+ },
+ {
+ "id": "layer_main_image",
+ "type": "mainImage",
+ "zIndex": 1,
+ "position": { "x": 0.5, "y": 0.28, "w": 0.85, "h": 0.38 },
+ "anchor": "center",
+ "required": false,
+ "style": {
+ "borderRadius": 24,
+ "objectFit": "cover",
+ "shadow": {
+ "enabled": true,
+ "blur": 40,
+ "spread": 0,
+ "offsetX": 0,
+ "offsetY": 20,
+ "color": "rgba(0,0,0,0.6)"
+ },
+ "border": {
+ "enabled": false,
+ "width": 4,
+ "color": "#ffffff"
+ }
+ }
+ },
+ {
+ "id": "layer_title",
+ "type": "title",
+ "zIndex": 10,
+ "position": { "x": 0.5, "y": 0.54, "w": 0.92, "h": 0.12 },
+ "anchor": "center",
+ "required": true,
+ "maxLines": 2,
+ "style": {
+ "fontFamily": "Montserrat",
+ "fontWeight": 900,
+ "fontSize": 82,
+ "fontSizeMin": 48,
+ "fontSizeMax": 120,
+ "autoFit": true,
+ "color": "#ffffff",
+ "textAlign": "center",
+ "textTransform": "uppercase",
+ "letterSpacing": 6,
+ "lineHeight": 1.05,
+ "shadow": {
+ "enabled": true,
+ "blur": 15,
+ "offsetX": 3,
+ "offsetY": 3,
+ "color": "rgba(0,0,0,0.9)"
+ },
+ "stroke": {
+ "enabled": true,
+ "width": 3,
+ "color": "rgba(0,0,0,0.5)"
+ }
+ }
+ },
+ {
+ "id": "layer_subtitle",
+ "type": "subtitle",
+ "zIndex": 10,
+ "position": { "x": 0.5, "y": 0.635, "w": 0.85, "h": 0.05 },
+ "anchor": "center",
+ "required": false,
+ "style": {
+ "fontFamily": "Open Sans",
+ "fontWeight": 400,
+ "fontSize": 32,
+ "color": "#f0f0f0",
+ "textAlign": "center",
+ "letterSpacing": 2,
+ "lineHeight": 1.3,
+ "shadow": {
+ "enabled": true,
+ "blur": 8,
+ "offsetX": 1,
+ "offsetY": 1,
+ "color": "rgba(0,0,0,0.7)"
+ }
+ }
+ },
+ {
+ "id": "layer_event_date",
+ "type": "eventDate",
+ "zIndex": 10,
+ "position": { "x": 0.5, "y": 0.72, "w": 0.9, "h": 0.06 },
+ "anchor": "center",
+ "required": true,
+ "style": {
+ "fontFamily": "Bebas Neue",
+ "fontWeight": 400,
+ "fontSize": 56,
+ "color": "#ffd700",
+ "textAlign": "center",
+ "letterSpacing": 4,
+ "textTransform": "uppercase",
+ "shadow": {
+ "enabled": true,
+ "blur": 10,
+ "offsetX": 2,
+ "offsetY": 2,
+ "color": "rgba(0,0,0,0.8)"
+ }
+ }
+ },
+ {
+ "id": "layer_location",
+ "type": "location",
+ "zIndex": 10,
+ "position": { "x": 0.5, "y": 0.79, "w": 0.85, "h": 0.05 },
+ "anchor": "center",
+ "required": true,
+ "icon": {
+ "enabled": true,
+ "name": "location_on",
+ "size": 28,
+ "color": "#e74c3c"
+ },
+ "style": {
+ "fontFamily": "Open Sans",
+ "fontWeight": 600,
+ "fontSize": 28,
+ "color": "#ffffff",
+ "textAlign": "center",
+ "letterSpacing": 1
+ }
+ },
+ {
+ "id": "layer_contacts",
+ "type": "contacts",
+ "zIndex": 10,
+ "position": { "x": 0.5, "y": 0.86, "w": 0.9, "h": 0.04 },
+ "anchor": "center",
+ "required": false,
+ "style": {
+ "fontFamily": "Open Sans",
+ "fontWeight": 400,
+ "fontSize": 22,
+ "color": "#cccccc",
+ "textAlign": "center",
+ "letterSpacing": 0.5
+ }
+ },
+ {
+ "id": "layer_extra_text",
+ "type": "extraText",
+ "zIndex": 10,
+ "position": { "x": 0.5, "y": 0.91, "w": 0.85, "h": 0.03 },
+ "anchor": "center",
+ "required": false,
+ "style": {
+ "fontFamily": "Open Sans",
+ "fontWeight": 300,
+ "fontSize": 18,
+ "fontStyle": "italic",
+ "color": "#aaaaaa",
+ "textAlign": "center"
+ }
+ }
+ ],
+
+ "logoSlots": {
+ "enabled": true,
+ "maxCount": 3,
+ "collapseIfEmpty": true,
+ "slots": [
+ {
+ "id": "logo_slot_1",
+ "position": { "x": 0.12, "y": 0.96, "w": 0.12, "h": 0.05 },
+ "anchor": "bottom-left",
+ "style": { "objectFit": "contain", "opacity": 0.9 }
+ },
+ {
+ "id": "logo_slot_2",
+ "position": { "x": 0.5, "y": 0.96, "w": 0.12, "h": 0.05 },
+ "anchor": "bottom-center",
+ "style": { "objectFit": "contain", "opacity": 0.9 }
+ },
+ {
+ "id": "logo_slot_3",
+ "position": { "x": 0.88, "y": 0.96, "w": 0.12, "h": 0.05 },
+ "anchor": "bottom-right",
+ "style": { "objectFit": "contain", "opacity": 0.9 }
+ }
+ ]
+ },
+
+ "palette": {
+ "primary": "#e94560",
+ "secondary": "#0f3460",
+ "accent": "#ffd700",
+ "background": "#1a1a2e",
+ "text": "#ffffff",
+ "textSecondary": "#cccccc",
+ "textMuted": "#888888"
+ },
+
+ "typography": {
+ "titleFont": "Montserrat",
+ "headingFont": "Bebas Neue",
+ "bodyFont": "Open Sans",
+ "accentFont": "Playfair Display"
+ },
+
+ "defaultAiPromptHints": {
+ "backgroundImage": "atmospheric outdoor scene, nature, forest, autumn colors, cinematic lighting, no text, no letters",
+ "mainImage": "detailed illustration, high quality, vibrant colors, no text"
+ },
+
+ "metadata": {
+ "author": "System",
+ "version": "1.0.0",
+ "tags": ["natura", "outdoor", "autunno", "sagra"]
+ },
+
+ "createdAt": "2025-01-15T10:00:00.000Z",
+ "updatedAt": "2025-01-15T10:00:00.000Z"
+}
\ No newline at end of file
diff --git a/src/middleware/upload.js b/src/middleware/upload.js
new file mode 100644
index 0000000..5cd6d83
--- /dev/null
+++ b/src/middleware/upload.js
@@ -0,0 +1,42 @@
+const multer = require('multer');
+const path = require('path');
+const crypto = require('crypto');
+
+const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
+
+const storage = multer.diskStorage({
+ destination: (req, file, cb) => {
+ cb(null, UPLOAD_DIR);
+ },
+ filename: (req, file, cb) => {
+ const uniqueId = crypto.randomBytes(8).toString('hex');
+ const ext = path.extname(file.originalname);
+ cb(null, `${Date.now()}_${uniqueId}${ext}`);
+ }
+});
+
+const fileFilter = (req, file, cb) => {
+ const allowedTypes = [
+ 'image/jpeg',
+ 'image/png',
+ 'image/gif',
+ 'image/webp',
+ 'image/svg+xml'
+ ];
+
+ if (allowedTypes.includes(file.mimetype)) {
+ cb(null, true);
+ } else {
+ cb(new Error('Tipo file non supportato'), false);
+ }
+};
+
+const upload = multer({
+ storage,
+ fileFilter,
+ limits: {
+ fileSize: 20 * 1024 * 1024 // 20MB max
+ }
+});
+
+module.exports = upload;
\ No newline at end of file
diff --git a/src/models/Asset.js b/src/models/Asset.js
new file mode 100644
index 0000000..e7aba90
--- /dev/null
+++ b/src/models/Asset.js
@@ -0,0 +1,137 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+// Sub-schema: File Info
+const FileInfoSchema = new Schema({
+ path: { type: String, required: true },
+ url: { type: String },
+ thumbnailPath: { type: String },
+ thumbnailUrl: { type: String },
+ originalName: { type: String },
+ mimeType: { type: String, required: true },
+ size: { type: Number }, // bytes
+ dimensions: {
+ width: { type: Number },
+ height: { type: Number }
+ }
+}, { _id: false });
+
+// Sub-schema: AI Generation Params
+const AiGenerationSchema = new Schema({
+ prompt: { type: String, required: true },
+ negativePrompt: { type: String },
+ provider: {
+ type: String,
+ required: true,
+ enum: ['hf', 'fal', 'ideogram', 'openai', 'stability', 'midjourney']
+ },
+ model: { type: String },
+ seed: { type: Number },
+ steps: { type: Number },
+ cfg: { type: Number },
+ requestedSize: { type: String },
+ actualSize: { type: String },
+ aspectRatio: { type: String },
+ styleType: { type: String },
+ generationTime: { type: Number }, // ms
+ cost: { type: Number, default: 0 },
+ rawResponse: { type: Schema.Types.Mixed }
+}, { _id: false });
+
+// Sub-schema: Usage Tracking
+const UsageTrackingSchema = new Schema({
+ usedInPosters: [{ type: Schema.Types.ObjectId, ref: 'Poster' }],
+ usedInTemplates: [{ type: Schema.Types.ObjectId, ref: 'Template' }],
+ usageCount: { type: Number, default: 0 }
+}, { _id: false });
+
+// Sub-schema: Asset Metadata
+const AssetMetadataSchema = new Schema({
+ userId: { type: Schema.Types.ObjectId, ref: 'User', index: true },
+ projectId: { type: Schema.Types.ObjectId, ref: 'Project' },
+ tags: [{ type: String }],
+ description: { type: String },
+ isReusable: { type: Boolean, default: true },
+ isPublic: { type: Boolean, default: false }
+}, { _id: false });
+
+// MAIN SCHEMA: Asset
+const AssetSchema = new Schema({
+ type: {
+ type: String,
+ required: true,
+ enum: ['image', 'logo', 'icon', 'font']
+ },
+ category: {
+ type: String,
+ required: true,
+ enum: ['background', 'main', 'logo', 'decoration', 'overlay', 'other'],
+ index: true
+ },
+ sourceType: {
+ type: String,
+ required: true,
+ enum: ['upload', 'ai', 'library', 'url'],
+ index: true
+ },
+
+ file: { type: FileInfoSchema, required: true },
+ aiGeneration: { type: AiGenerationSchema },
+
+ usage: { type: UsageTrackingSchema, default: () => ({}) },
+ metadata: { type: AssetMetadataSchema, default: () => ({}) },
+
+ status: {
+ type: String,
+ enum: ['processing', 'ready', 'error', 'deleted'],
+ default: 'ready'
+ },
+ errorMessage: { type: String }
+}, {
+ timestamps: true,
+ toJSON: { virtuals: true }
+});
+
+// Indexes
+AssetSchema.index({ 'metadata.userId': 1, category: 1 });
+AssetSchema.index({ 'metadata.tags': 1 });
+AssetSchema.index({ sourceType: 1, status: 1 });
+
+// Virtual: isAiGenerated
+AssetSchema.virtual('isAiGenerated').get(function() {
+ return this.sourceType === 'ai';
+});
+
+// Methods
+AssetSchema.methods.addUsage = async function(posterId, type = 'poster') {
+ if (type === 'poster' && !this.usage.usedInPosters.includes(posterId)) {
+ this.usage.usedInPosters.push(posterId);
+ } else if (type === 'template' && !this.usage.usedInTemplates.includes(posterId)) {
+ this.usage.usedInTemplates.push(posterId);
+ }
+ this.usage.usageCount = this.usage.usedInPosters.length + this.usage.usedInTemplates.length;
+ return this.save();
+};
+
+AssetSchema.methods.getPublicUrl = function() {
+ return this.file.url || `/api/assets/${this._id}/file`;
+};
+
+// Statics
+AssetSchema.statics.findByUser = function(userId, category = null) {
+ const query = { 'metadata.userId': userId, status: 'ready' };
+ if (category) query.category = category;
+ return this.find(query).sort({ createdAt: -1 });
+};
+
+AssetSchema.statics.findReusable = function(userId, category = null) {
+ const query = {
+ 'metadata.userId': userId,
+ 'metadata.isReusable': true,
+ status: 'ready'
+ };
+ if (category) query.category = category;
+ return this.find(query).sort({ 'usage.usageCount': -1 });
+};
+
+module.exports = mongoose.model('Asset', AssetSchema);
\ No newline at end of file
diff --git a/src/models/Poster.js b/src/models/Poster.js
new file mode 100644
index 0000000..f64b082
--- /dev/null
+++ b/src/models/Poster.js
@@ -0,0 +1,262 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+// Sub-schema: Content
+const PosterContentSchema = new Schema({
+ title: { type: String, maxlength: 500 },
+ subtitle: { type: String, maxlength: 500 },
+ eventDate: { type: String, maxlength: 200 },
+ eventTime: { type: String, maxlength: 100 },
+ location: { type: String, maxlength: 500 },
+ contacts: { type: String, maxlength: 1000 },
+ extraText: [{ type: String }],
+ customFields: { type: Map, of: String }
+}, { _id: false });
+
+// Sub-schema: Asset AI Params (embedded)
+const EmbeddedAiParamsSchema = new Schema({
+ prompt: { type: String },
+ negativePrompt: { type: String },
+ provider: { type: String },
+ model: { type: String },
+ seed: { type: Number },
+ steps: { type: Number },
+ cfg: { type: Number },
+ size: { type: String },
+ generatedAt: { type: Date }
+}, { _id: false });
+
+// Sub-schema: Poster Asset Reference
+const PosterAssetSchema = new Schema({
+ id: { type: String },
+ assetId: { type: Schema.Types.ObjectId, ref: 'Asset' },
+ slotId: { type: String }, // per loghi
+ sourceType: { type: String, enum: ['upload', 'ai', 'library', 'url'] },
+ url: { type: String },
+ thumbnailUrl: { type: String },
+ originalName: { type: String },
+ mimeType: { type: String },
+ size: { type: Number },
+ dimensions: {
+ width: { type: Number },
+ height: { type: Number }
+ },
+ aiParams: EmbeddedAiParamsSchema
+}, { _id: false });
+
+// Sub-schema: Assets Container
+const PosterAssetsSchema = new Schema({
+ backgroundImage: PosterAssetSchema,
+ mainImage: PosterAssetSchema,
+ logos: [PosterAssetSchema]
+}, { _id: false });
+
+// Sub-schema: Layer Override Style
+const LayerOverrideStyleSchema = new Schema({
+ fontSize: { type: Number },
+ color: { type: String },
+ fontWeight: { type: Number },
+ opacity: { type: Number },
+ // altri override possibili
+}, { _id: false });
+
+// Sub-schema: Layer Override
+const LayerOverrideSchema = new Schema({
+ position: {
+ x: { type: Number },
+ y: { type: Number },
+ w: { type: Number },
+ h: { type: Number }
+ },
+ visible: { type: Boolean },
+ style: LayerOverrideStyleSchema
+}, { _id: false });
+
+// Sub-schema: Render Output File
+const RenderOutputFileSchema = new Schema({
+ path: { type: String, required: true },
+ url: { type: String },
+ size: { type: Number },
+ quality: { type: Number }
+}, { _id: false });
+
+// Sub-schema: Render Output
+const RenderOutputSchema = new Schema({
+ png: RenderOutputFileSchema,
+ jpg: RenderOutputFileSchema,
+ webp: RenderOutputFileSchema,
+ pdf: RenderOutputFileSchema,
+ dimensions: {
+ width: { type: Number },
+ height: { type: Number }
+ },
+ renderedAt: { type: Date }
+}, { _id: false });
+
+// Sub-schema: History Entry
+const HistoryEntrySchema = new Schema({
+ action: {
+ type: String,
+ required: true,
+ enum: ['created', 'updated', 'ai_background_generated', 'ai_main_generated', 'rendered', 'downloaded', 'shared', 'deleted']
+ },
+ timestamp: { type: Date, default: Date.now },
+ userId: { type: Schema.Types.ObjectId, ref: 'User' },
+ details: { type: Schema.Types.Mixed }
+}, { _id: false });
+
+// Sub-schema: Poster Metadata
+const PosterMetadataSchema = new Schema({
+ userId: { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
+ projectId: { type: Schema.Types.ObjectId, ref: 'Project' },
+ tags: [{ type: String }],
+ isPublic: { type: Boolean, default: false },
+ isFavorite: { type: Boolean, default: false },
+ viewCount: { type: Number, default: 0 },
+ downloadCount: { type: Number, default: 0 }
+}, { _id: false });
+
+// MAIN SCHEMA: Poster
+const PosterSchema = new Schema({
+ templateId: {
+ type: Schema.Types.ObjectId,
+ ref: 'Template',
+ required: true,
+ index: true
+ },
+ templateSnapshot: { type: Schema.Types.Mixed }, // copia del template al momento della creazione
+
+ name: { type: String, required: true, trim: true, maxlength: 300 },
+ description: { type: String, maxlength: 1000 },
+
+ status: {
+ type: String,
+ enum: ['draft', 'processing', 'completed', 'error'],
+ default: 'draft',
+ index: true
+ },
+
+ content: { type: PosterContentSchema, required: true },
+ assets: { type: PosterAssetsSchema, default: () => ({}) },
+ layerOverrides: { type: Map, of: LayerOverrideSchema, default: () => new Map() },
+
+ renderOutput: RenderOutputSchema,
+ renderEngineVersion: { type: String, default: '1.0.0' },
+
+ history: [HistoryEntrySchema],
+ metadata: { type: PosterMetadataSchema, required: true },
+
+ errorMessage: { type: String },
+
+ // Campi dalla tua bozza originale
+ originalPrompt: { type: String }, // prompt completo usato
+ styleUsed: { type: String },
+ aspectRatio: { type: String },
+ provider: { type: String }
+}, {
+ timestamps: true,
+ toJSON: { virtuals: true },
+ toObject: { virtuals: true }
+});
+
+// Indexes
+PosterSchema.index({ 'metadata.userId': 1, status: 1 });
+PosterSchema.index({ 'metadata.tags': 1 });
+PosterSchema.index({ 'metadata.isFavorite': 1, 'metadata.userId': 1 });
+PosterSchema.index({ createdAt: -1 });
+PosterSchema.index({ name: 'text', description: 'text' });
+
+// Virtual: isCompleted
+PosterSchema.virtual('isCompleted').get(function() {
+ return this.status === 'completed' && this.renderOutput?.png?.path;
+});
+
+// Virtual: downloadUrl
+PosterSchema.virtual('downloadUrl').get(function() {
+ if (this.renderOutput?.png?.path) {
+ return `/api/posters/${this._id}/download/png`;
+ }
+ return null;
+});
+
+// Pre-save: aggiorna history
+PosterSchema.pre('save', function(next) {
+ if (this.isNew) {
+ this.history = this.history || [];
+ this.history.push({
+ action: 'created',
+ timestamp: new Date(),
+ userId: this.metadata.userId
+ });
+ }
+ next();
+});
+
+// Methods
+PosterSchema.methods.addHistory = function(action, details = {}) {
+ this.history.push({
+ action,
+ timestamp: new Date(),
+ userId: this.metadata.userId,
+ details
+ });
+ return this;
+};
+
+PosterSchema.methods.setRenderOutput = function(outputData) {
+ this.renderOutput = {
+ ...outputData,
+ renderedAt: new Date()
+ };
+ this.status = 'completed';
+ this.addHistory('rendered', { duration: outputData.duration });
+ return this;
+};
+
+PosterSchema.methods.setError = function(errorMessage) {
+ this.status = 'error';
+ this.errorMessage = errorMessage;
+ return this;
+};
+
+PosterSchema.methods.incrementDownload = async function() {
+ this.metadata.downloadCount = (this.metadata.downloadCount || 0) + 1;
+ this.addHistory('downloaded');
+ return this.save();
+};
+
+PosterSchema.methods.toggleFavorite = async function() {
+ this.metadata.isFavorite = !this.metadata.isFavorite;
+ return this.save();
+};
+
+// Statics
+PosterSchema.statics.findByUser = function(userId, options = {}) {
+ const query = { 'metadata.userId': userId };
+ if (options.status) query.status = options.status;
+ if (options.isFavorite) query['metadata.isFavorite'] = true;
+
+ return this.find(query)
+ .populate('templateId', 'name templateType thumbnailUrl')
+ .sort({ createdAt: -1 })
+ .limit(options.limit || 50);
+};
+
+PosterSchema.statics.findFavorites = function(userId) {
+ return this.find({
+ 'metadata.userId': userId,
+ 'metadata.isFavorite': true
+ }).sort({ updatedAt: -1 });
+};
+
+PosterSchema.statics.findRecent = function(userId, limit = 10) {
+ return this.find({
+ 'metadata.userId': userId,
+ status: 'completed'
+ })
+ .sort({ createdAt: -1 })
+ .limit(limit)
+ .select('name renderOutput.png.url thumbnailUrl createdAt');
+};
+
+module.exports = mongoose.model('Poster', PosterSchema);
\ No newline at end of file
diff --git a/src/models/Template.js b/src/models/Template.js
new file mode 100644
index 0000000..4ed9184
--- /dev/null
+++ b/src/models/Template.js
@@ -0,0 +1,253 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+// Sub-schema: Posizione layer
+const PositionSchema = new Schema({
+ x: { type: Number, required: true, min: 0, max: 1 },
+ y: { type: Number, required: true, min: 0, max: 1 },
+ w: { type: Number, required: true, min: 0, max: 1 },
+ h: { type: Number, required: true, min: 0, max: 1 }
+}, { _id: false });
+
+// Sub-schema: Ombra
+const ShadowSchema = new Schema({
+ enabled: { type: Boolean, default: false },
+ blur: { type: Number, default: 10 },
+ spread: { type: Number, default: 0 },
+ offsetX: { type: Number, default: 0 },
+ offsetY: { type: Number, default: 4 },
+ color: { type: String, default: 'rgba(0,0,0,0.5)' }
+}, { _id: false });
+
+// Sub-schema: Stroke
+const StrokeSchema = new Schema({
+ enabled: { type: Boolean, default: false },
+ width: { type: Number, default: 2 },
+ color: { type: String, default: '#000000' }
+}, { _id: false });
+
+// Sub-schema: Border
+const BorderSchema = new Schema({
+ enabled: { type: Boolean, default: false },
+ width: { type: Number, default: 2 },
+ color: { type: String, default: '#ffffff' },
+ style: { type: String, enum: ['solid', 'dashed', 'dotted'], default: 'solid' }
+}, { _id: false });
+
+// Sub-schema: Gradient Stop
+const GradientStopSchema = new Schema({
+ position: { type: Number, required: true, min: 0, max: 1 },
+ color: { type: String, required: true }
+}, { _id: false });
+
+// Sub-schema: Overlay
+const OverlaySchema = new Schema({
+ enabled: { type: Boolean, default: false },
+ type: { type: String, enum: ['solid', 'gradient'], default: 'gradient' },
+ color: { type: String },
+ direction: { type: String, default: 'to-bottom' },
+ stops: [GradientStopSchema]
+}, { _id: false });
+
+// Sub-schema: Fallback
+const FallbackSchema = new Schema({
+ type: { type: String, enum: ['solid', 'gradient'], default: 'solid' },
+ color: { type: String },
+ direction: { type: String },
+ colors: [{ type: String }]
+}, { _id: false });
+
+// Sub-schema: Icon
+const IconSchema = new Schema({
+ enabled: { type: Boolean, default: false },
+ name: { type: String },
+ size: { type: Number, default: 24 },
+ color: { type: String, default: '#ffffff' }
+}, { _id: false });
+
+// Sub-schema: Stile Layer (unificato per immagini e testi)
+const LayerStyleSchema = new Schema({
+ // Comuni
+ opacity: { type: Number, default: 1, min: 0, max: 1 },
+
+ // Per immagini
+ objectFit: { type: String, enum: ['cover', 'contain', 'fill', 'none'], default: 'cover' },
+ blur: { type: Number, default: 0 },
+ borderRadius: { type: Number, default: 0 },
+ overlay: OverlaySchema,
+ border: BorderSchema,
+
+ // Per testi
+ fontFamily: { type: String },
+ fontWeight: { type: Number, default: 400 },
+ fontSize: { type: Number },
+ fontSizeMin: { type: Number },
+ fontSizeMax: { type: Number },
+ autoFit: { type: Boolean, default: false },
+ fontStyle: { type: String, enum: ['normal', 'italic'], default: 'normal' },
+ color: { type: String },
+ textAlign: { type: String, enum: ['left', 'center', 'right'], default: 'center' },
+ textTransform: { type: String, enum: ['none', 'uppercase', 'lowercase', 'capitalize'], default: 'none' },
+ letterSpacing: { type: Number, default: 0 },
+ lineHeight: { type: Number, default: 1.2 },
+
+ // Effetti
+ shadow: ShadowSchema,
+ stroke: StrokeSchema
+}, { _id: false });
+
+// Sub-schema: Layer
+const LayerSchema = new Schema({
+ id: { type: String, required: true },
+ type: {
+ type: String,
+ required: true,
+ enum: ['backgroundImage', 'mainImage', 'logo', 'title', 'subtitle', 'eventDate', 'eventTime', 'location', 'contacts', 'extraText', 'customText', 'customImage', 'shape', 'divider']
+ },
+ zIndex: { type: Number, default: 0 },
+ position: { type: PositionSchema, required: true },
+ anchor: {
+ type: String,
+ enum: ['top-left', 'top-center', 'top-right', 'center-left', 'center', 'center-right', 'bottom-left', 'bottom-center', 'bottom-right'],
+ default: 'center'
+ },
+ required: { type: Boolean, default: false },
+ visible: { type: Boolean, default: true },
+ locked: { type: Boolean, default: false },
+ maxLines: { type: Number },
+ fallback: FallbackSchema,
+ icon: IconSchema,
+ style: { type: LayerStyleSchema, default: () => ({}) }
+}, { _id: false });
+
+// Sub-schema: Logo Slot
+const LogoSlotSchema = new Schema({
+ id: { type: String, required: true },
+ position: { type: PositionSchema, required: true },
+ anchor: { type: String, default: 'center' },
+ style: { type: LayerStyleSchema, default: () => ({}) }
+}, { _id: false });
+
+// Sub-schema: Logo Slots Config
+const LogoSlotsConfigSchema = new Schema({
+ enabled: { type: Boolean, default: true },
+ maxCount: { type: Number, default: 3, min: 1, max: 10 },
+ collapseIfEmpty: { type: Boolean, default: true },
+ slots: [LogoSlotSchema]
+}, { _id: false });
+
+// Sub-schema: Format
+const FormatSchema = new Schema({
+ preset: { type: String, default: 'custom' }, // A4, A3, Instagram, Facebook, custom
+ width: { type: Number, required: true },
+ height: { type: Number, required: true },
+ unit: { type: String, enum: ['px', 'mm', 'in'], default: 'px' },
+ dpi: { type: Number, default: 300 }
+}, { _id: false });
+
+// Sub-schema: Safe Area
+const SafeAreaSchema = new Schema({
+ top: { type: Number, default: 0, min: 0, max: 0.5 },
+ right: { type: Number, default: 0, min: 0, max: 0.5 },
+ bottom: { type: Number, default: 0, min: 0, max: 0.5 },
+ left: { type: Number, default: 0, min: 0, max: 0.5 }
+}, { _id: false });
+
+// Sub-schema: Palette
+const PaletteSchema = new Schema({
+ primary: { type: String, default: '#e94560' },
+ secondary: { type: String, default: '#0f3460' },
+ accent: { type: String, default: '#ffd700' },
+ background: { type: String, default: '#1a1a2e' },
+ text: { type: String, default: '#ffffff' },
+ textSecondary: { type: String, default: '#cccccc' },
+ textMuted: { type: String, default: '#888888' }
+}, { _id: false });
+
+// Sub-schema: Typography
+const TypographySchema = new Schema({
+ titleFont: { type: String, default: 'Montserrat' },
+ headingFont: { type: String, default: 'Bebas Neue' },
+ bodyFont: { type: String, default: 'Open Sans' },
+ accentFont: { type: String, default: 'Playfair Display' }
+}, { _id: false });
+
+// Sub-schema: AI Prompt Hints
+const AiPromptHintsSchema = new Schema({
+ backgroundImage: { type: String },
+ mainImage: { type: String }
+}, { _id: false });
+
+// Sub-schema: Metadata
+const TemplateMetadataSchema = new Schema({
+ author: { type: String, default: 'System' },
+ version: { type: String, default: '1.0.0' },
+ tags: [{ type: String }],
+ isPublic: { type: Boolean, default: false },
+ usageCount: { type: Number, default: 0 }
+}, { _id: false });
+
+// MAIN SCHEMA: Template
+const TemplateSchema = new Schema({
+ name: { type: String, required: true, trim: true, maxlength: 200 },
+ templateType: { type: String, required: true, trim: true, index: true },
+ description: { type: String, maxlength: 1000 },
+
+ format: { type: FormatSchema, required: true },
+ safeArea: { type: SafeAreaSchema, default: () => ({}) },
+ backgroundColor: { type: String, default: '#1a1a2e' },
+
+ layers: { type: [LayerSchema], required: true, validate: [arr => arr.length > 0, 'Almeno un layer richiesto'] },
+ logoSlots: { type: LogoSlotsConfigSchema, default: () => ({}) },
+
+ palette: { type: PaletteSchema, default: () => ({}) },
+ typography: { type: TypographySchema, default: () => ({}) },
+ defaultAiPromptHints: { type: AiPromptHintsSchema, default: () => ({}) },
+
+ previewUrl: { type: String },
+ thumbnailUrl: { type: String },
+
+ metadata: { type: TemplateMetadataSchema, default: () => ({}) },
+
+ isActive: { type: Boolean, default: true },
+ userId: { type: Schema.Types.ObjectId, ref: 'User', index: true }
+}, {
+ timestamps: true,
+ toJSON: { virtuals: true },
+ toObject: { virtuals: true }
+});
+
+// Indexes
+TemplateSchema.index({ templateType: 1, isActive: 1 });
+TemplateSchema.index({ 'metadata.tags': 1 });
+TemplateSchema.index({ name: 'text', description: 'text', templateType: 'text' });
+
+// Virtual: layer count
+TemplateSchema.virtual('layerCount').get(function() {
+ return this.layers ? this.layers.length : 0;
+});
+
+// Methods
+TemplateSchema.methods.getLayerById = function(layerId) {
+ return this.layers.find(l => l.id === layerId);
+};
+
+TemplateSchema.methods.getLayersByType = function(type) {
+ return this.layers.filter(l => l.type === type);
+};
+
+TemplateSchema.methods.incrementUsage = async function() {
+ this.metadata.usageCount = (this.metadata.usageCount || 0) + 1;
+ return this.save();
+};
+
+// Statics
+TemplateSchema.statics.findByType = function(templateType) {
+ return this.find({ templateType, isActive: true }).sort({ 'metadata.usageCount': -1 });
+};
+
+TemplateSchema.statics.findPublic = function() {
+ return this.find({ 'metadata.isPublic': true, isActive: true });
+};
+
+module.exports = mongoose.model('Template', TemplateSchema);
\ No newline at end of file
diff --git a/src/models/account.js b/src/models/account.js
index 6a1d8bd..5df7288 100755
--- a/src/models/account.js
+++ b/src/models/account.js
@@ -32,6 +32,12 @@ const AccountSchema = new Schema({
numtransactions: {
type: Number,
},
+ sent: {
+ type: Number,
+ },
+ received: {
+ type: Number,
+ },
username: {
type: String,
},
@@ -242,6 +248,12 @@ AccountSchema.statics.addtoSaldo = async function (myaccount, amount, mitt) {
myaccountupdate.saldo = myaccount.saldo;
myaccountupdate.totTransato = myaccount.totTransato;
myaccountupdate.numtransactions = myaccount.numtransactions;
+ if (amount > 0) {
+ myaccountupdate.received += 1;
+ } else {
+ myaccountupdate.sent += 1;
+ }
+
myaccountupdate.date_updated = myaccount.date_updated;
const ris = await Account.updateOne(
@@ -324,6 +336,8 @@ AccountSchema.statics.getAccountByUsernameAndCircuitId = async function (
username_admin_abilitante: '',
qta_maxConcessa: 0,
totTransato: 0,
+ sent: 0,
+ received: 0,
numtransactions: 0,
totTransato_pend: 0,
});
diff --git a/src/models/circuit.js b/src/models/circuit.js
index 409a815..c5e89e2 100755
--- a/src/models/circuit.js
+++ b/src/models/circuit.js
@@ -87,6 +87,9 @@ const CircuitSchema = new Schema({
totTransato: {
type: Number,
},
+ numTransazioni: {
+ type: Number,
+ },
nome_valuta: {
type: String,
maxlength: 20,
@@ -327,6 +330,7 @@ CircuitSchema.statics.getWhatToShow = function (idapp, username) {
numMembers: 1,
totCircolante: 1,
totTransato: 1,
+ numTransazioni: 1,
systemUserId: 1,
createdBy: 1,
date_created: 1,
@@ -412,6 +416,7 @@ CircuitSchema.statics.getWhatToShow_Unknown = function (idapp, username) {
nome_valuta: 1,
totCircolante: 1,
totTransato: 1,
+ numTransazioni: 1,
fido_scoperto_default: 1,
fido_scoperto_default_grp: 1,
qta_max_default_grp: 1,
@@ -825,6 +830,7 @@ CircuitSchema.statics.sendCoins = async function (onlycheck, idapp, usernameOrig
const circolanteAtt = this.getCircolanteSingolaTransaz(accountorigTable, accountdestTable);
// Somma di tutte le transazioni
+ circuittable.numTransazioni += 1;
circuittable.totTransato += myqty;
// circuittable.totCircolante = circuittable.totCircolante + (circolanteAtt - circolantePrec);
circuittable.totCircolante = await Account.calcTotCircolante(idapp, circuittable._id);
@@ -901,7 +907,14 @@ CircuitSchema.statics.sendCoins = async function (onlycheck, idapp, usernameOrig
let myuserDest = await User.getUserByUsername(idapp, extrarec.dest);
// Invia una email al destinatario !
- await sendemail.sendEmail_RisRicevuti(myuserDest.lang, myuserDest, myuserDest.email, idapp, paramsrec, extrarec);
+ await sendemail.sendEmail_RisRicevuti(
+ myuserDest.lang,
+ myuserDest,
+ myuserDest.email,
+ idapp,
+ paramsrec,
+ extrarec
+ );
} else if (extrarec.groupdest || extrarec.contoComDest) {
const groupDestoContoCom = extrarec.groupdest
? extrarec.groupdest
@@ -1047,16 +1060,16 @@ CircuitSchema.statics.getListAdminsByCircuitPath = async function (idapp, circui
let adminObjects = circuit && circuit.admins ? circuit.admins : [];
// Aggiungi USER_ADMIN_CIRCUITS come oggetti
- let systemAdmins = shared_consts.USER_ADMIN_CIRCUITS.map(username => ({
+ let systemAdmins = shared_consts.USER_ADMIN_CIRCUITS.map((username) => ({
username,
date: null,
- _id: null
+ _id: null,
}));
// Unisci e rimuovi duplicati per username
let allAdmins = [...adminObjects, ...systemAdmins];
- let uniqueAdmins = allAdmins.filter((admin, index, self) =>
- index === self.findIndex(a => a.username === admin.username)
+ let uniqueAdmins = allAdmins.filter(
+ (admin, index, self) => index === self.findIndex((a) => a.username === admin.username)
);
return uniqueAdmins;
@@ -1190,6 +1203,7 @@ CircuitSchema.statics.createCircuitIfNotExist = async function (req, idapp, prov
qta_max_default_grp: shared_consts.CIRCUIT_PARAMS.SCOPERTO_MAX_GRP,
valuta_per_euro: 1,
totTransato: 0,
+ numTransazioni: 0,
totCircolante: 0,
date_created: new Date(),
admins: admins.map((username) => ({ username })),
@@ -1388,7 +1402,12 @@ CircuitSchema.statics.setFido = async function (idapp, username, circuitName, gr
const ris = await Account.updateFido(idapp, username, groupname, circuitId, fido, username_action);
if (ris) {
- return { qta_maxConcessa: qtamax, fidoConcesso: fido, username_admin_abilitante: username_action, changed: variato || (ris && ris.modifiedCount > 0) };
+ return {
+ qta_maxConcessa: qtamax,
+ fidoConcesso: fido,
+ username_admin_abilitante: username_action,
+ changed: variato || (ris && ris.modifiedCount > 0),
+ };
}
}
}
@@ -1441,7 +1460,7 @@ CircuitSchema.statics.getFido = async function (idapp, username, circuitName, gr
return null;
};
-CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
+CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi, options) {
const { User } = require('../models/user');
const { MyGroup } = require('../models/mygroup');
const { SendNotif } = require('../models/sendnotif');
@@ -1540,7 +1559,7 @@ CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
let numtransazionitot = 0;
- const arrcircuits = await Circuit.find({ idapp }).lean();
+ const arrcircuits = await Circuit.find({ idapp });
for (const circuit of arrcircuits) {
let strusersnotinaCircuit = '';
let strusersnotExist = '';
@@ -1620,6 +1639,16 @@ CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
_id: null,
numtransactions: { $sum: 1 },
totTransato: { $sum: { $abs: '$amount' } },
+ sentCount: {
+ $sum: {
+ $cond: [{ $eq: ['$accountFromId', account._id] }, 1, 0],
+ },
+ },
+ receivedCount: {
+ $sum: {
+ $cond: [{ $eq: ['$accountToId', account._id] }, 1, 0],
+ },
+ },
saldo: {
$sum: {
$cond: [
@@ -1636,6 +1665,8 @@ CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
]);
let numtransactions = result && result.length > 0 ? result[0].numtransactions : 0;
+ let sentCount = result && result.length > 0 ? result[0].sentCount : 0;
+ let receivedCount = result && result.length > 0 ? result[0].receivedCount : 0;
let totTransato = result && result.length > 0 ? result[0].totTransato : 0;
let saldo = result && result.length > 0 ? result[0].saldo : 0;
@@ -1679,6 +1710,8 @@ CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
if (correggi) await Account.findOneAndUpdate({ _id: account._id }, { $set: { totTransato } });
}
+ await Account.findOneAndUpdate({ _id: account._id }, { $set: { sent: sentCount, received: receivedCount } });
+
saldotot += account.saldo;
// if (account.totTransato === NaN || account.totTransato === undefined)
@@ -1693,6 +1726,11 @@ CircuitSchema.statics.CheckTransazioniCircuiti = async function (correggi) {
// await account.calcPending();
ind++;
+ } // FINE ACCOUNT
+
+ if (options?.setnumtransaction) {
+ circuit.numTransazioni = numtransazionitot;
+ await circuit.save(); // salva su db
}
let numaccounts = accounts.length;
@@ -1876,6 +1914,11 @@ CircuitSchema.statics.getCircuitiExtraProvinciali = async function (idapp) {
return circuits;
};
+CircuitSchema.statics.ricalcolaNumTransazioni = async function (circuitId) {
+ const Circuit = this;
+
+ // +TODO: Ricalcola il numero delle transazioni avvenute
+};
CircuitSchema.statics.getCircuitoItalia = async function (idapp) {
const Circuit = this;
@@ -1884,6 +1927,13 @@ CircuitSchema.statics.getCircuitoItalia = async function (idapp) {
return circuit;
};
+CircuitSchema.statics.getSymbolByCircuitId = async function (circuitId) {
+ const Circuit = this;
+
+ const circuit = await Circuit.findOne({ _id: circuitId }, { symbol: 1});
+
+ return circuit?.symbol || '';
+};
CircuitSchema.statics.isEnableToReceiveEmailByExtraRec = async function (idapp, recnotif) {
let ricevo = true;
if (recnotif.tag === 'setfido') {
diff --git a/src/models/movement.js b/src/models/movement.js
index c1eadf7..1b2ecd0 100755
--- a/src/models/movement.js
+++ b/src/models/movement.js
@@ -106,6 +106,8 @@ MovementSchema.statics.addMov = async function (
idOrdersCart
) {
try {
+ const { Circuit } = require('./circuit');
+
// Only positive values
amount = Math.abs(amount);
diff --git a/src/models/site.js b/src/models/site.js
index 8cc1d7f..cb29b80 100755
--- a/src/models/site.js
+++ b/src/models/site.js
@@ -174,6 +174,7 @@ const SiteSchema = new Schema({
bookingEvents: { type: Boolean, default: false },
enableEcommerce: { type: Boolean, default: false },
enableAI: { type: Boolean, default: false },
+ enablePoster: { type: Boolean, default: false },
enableGroups: { type: Boolean, default: false },
enableCircuits: { type: Boolean, default: false },
enableGoods: { type: Boolean, default: false },
diff --git a/src/modules/CronMod.js b/src/modules/CronMod.js
index c3ca470..e1f380c 100644
--- a/src/modules/CronMod.js
+++ b/src/modules/CronMod.js
@@ -202,7 +202,7 @@ class CronMod {
} else if (mydata.dbop === 'RewriteCategESubCateg') {
const migration = require('../populate/migration-categories');
- ris = await migration.aggiornaCategorieESottoCategorie()
+ ris = await migration.aggiornaCategorieESottoCategorie();
} else if (mydata.dbop === 'ReplaceUsername') {
if (User.isAdmin(req.user.perm)) {
ris = globalTables.replaceUsername(req.body.idapp, mydata.search_username, mydata.replace_username);
@@ -270,6 +270,8 @@ class CronMod {
await Order.RemoveDeletedOrdersInOrderscart();
} else if (mydata.dbop === 'CheckTransazioniCircuiti') {
await Circuit.CheckTransazioniCircuiti(false);
+ } else if (mydata.dbop === 'CalcNumTransCircuiti') {
+ await Circuit.CheckTransazioniCircuiti(false, { setnumtransaction: true });
} else if (mydata.dbop === 'CorreggiTransazioniCircuiti') {
await Circuit.CheckTransazioniCircuiti(true);
} else if (mydata.dbop === 'RemovePendentTransactions') {
diff --git a/src/router/api_router.js b/src/router/api_router.js
index 6a1c652..2995897 100644
--- a/src/router/api_router.js
+++ b/src/router/api_router.js
@@ -2,8 +2,22 @@ const express = require('express');
const { authenticate, authenticate_noerror } = require('../middleware/authenticate');
const router = express.Router();
+
+const templatesRouter = require('../routes/templates');
+const postersRouter = require('../routes/posters');
+const assetsRouter = require('../routes/assets');
+
const PageView = require('../models/PageView');
+// const { Groq } = require('groq-sdk');
+
+const fal = require('@fal-ai/client');
+
+
+const imageGenerator = require('../services/imageGenerator'); // Assicurati che il percorso sia corretto
+
+const posterEditor = require('../services/PosterEditor'); // <--- Importa la nuova classe
+
const multer = require('multer');
const XLSX = require('xlsx');
@@ -19,6 +33,10 @@ const { MyElem } = require('../models/myelem');
const axios = require('axios');
+router.use('/templates', authenticate, templatesRouter);
+router.use('/posters', authenticate, postersRouter);
+router.use('/assets', authenticate, assetsRouter);
+
router.post('/test-lungo', authenticate, (req, res) => {
const timeout = req.body.timeout;
@@ -389,7 +407,6 @@ router.post('/search-books', authenticate, async (req, res) => {
let productfind = null;
for (let field of book) {
-
field = field.trim();
let valido = typeof field === 'string' && field.length > 4 && field.length < 50;
if (valido) {
@@ -494,4 +511,46 @@ router.post('/chatbot', authenticate, async (req, res) => {
}
});
+router.post('/generateposter', async (req, res) => {
+ const {
+ titolo, data, ora, luogo, descrizione, contatti, fotoDescrizione, stile,
+ provider = 'hf' // Default a HF (Gratis)
+ } = req.body;
+
+ // 1. Prompt per l'AI: Chiediamo SOLO lo sfondo, VIETIAMO il testo.
+ // Questo garantisce che Flux si concentri sulla bellezza dell'immagine.
+ const promptAI = `Vertical event poster background, ${stile || 'modern style, vivid colors'}.
+ Subject: ${fotoDescrizione || 'abstract artistic shapes'}.
+ Composition: Central empty space or clean layout suitable for overlaying text later.
+ NO TEXT, NO LETTERS, clean illustration, high quality, 4k.`;
+
+ try {
+ console.log('1. Generazione Sfondo AI...');
+ // Genera solo l'immagine base
+ const rawImageUrl = await imageGenerator.generate(provider, promptAI);
+
+ console.log('2. Composizione Grafica Testi...');
+ // Sovrapponi i testi con Canvas
+ const finalPosterBase64 = await posterEditor.createPoster(rawImageUrl, {
+ titolo,
+ data,
+ ora,
+ luogo,
+ contatti
+ });
+
+ res.json({
+ success: true,
+ imageUrl: finalPosterBase64, // Restituisce l'immagine completa in base64
+ step: 'AI + Canvas Composition'
+ });
+
+ } catch (err) {
+ console.error('Errore:', err.message);
+ res.status(500).json({ error: err.message });
+ }
+});
+
+
+
module.exports = router;
diff --git a/src/routes/assets.js b/src/routes/assets.js
new file mode 100644
index 0000000..4d3f587
--- /dev/null
+++ b/src/routes/assets.js
@@ -0,0 +1,21 @@
+const express = require('express');
+const router = express.Router();
+const assetController = require('../controllers/assetController');
+const { authenticate } = require('../middleware/authenticate');
+const upload = require('../middleware/upload');
+
+// Upload
+router.post('/upload', authenticate, upload.single('file'), assetController.upload);
+router.post('/upload-multiple', authenticate, upload.array('files', 10), assetController.uploadMultiple);
+
+// AI Generation
+router.post('/generate-ai', authenticate, assetController.generateAi);
+
+// CRUD
+router.get('/', authenticate, assetController.list);
+router.get('/:id', assetController.getById);
+router.get('/:id/file', assetController.getFile);
+router.get('/:id/thumbnail', assetController.getThumbnail);
+router.delete('/:id', authenticate, assetController.delete);
+
+module.exports = router;
\ No newline at end of file
diff --git a/src/routes/posters.js b/src/routes/posters.js
new file mode 100644
index 0000000..3d600c7
--- /dev/null
+++ b/src/routes/posters.js
@@ -0,0 +1,25 @@
+const express = require('express');
+const router = express.Router();
+const posterController = require('../controllers/posterController');
+const { authenticate } = require('../middleware/authenticate');
+const upload = require('../middleware/upload');
+
+// CRUD Posters
+router.post('/', authenticate, posterController.create);
+router.get('/', authenticate, posterController.list);
+router.get('/favorites', authenticate, posterController.listFavorites);
+router.get('/recent', authenticate, posterController.listRecent);
+router.get('/:id', authenticate, posterController.getById);
+router.put('/:id', authenticate, posterController.update);
+router.delete('/:id', authenticate, posterController.delete);
+
+// Azioni speciali
+router.post('/:id/render', authenticate, posterController.render);
+router.post('/:id/regenerate-ai', authenticate, posterController.regenerateAi);
+router.get('/:id/download/:format', posterController.download);
+router.post('/:id/favorite', authenticate, posterController.toggleFavorite);
+
+// Quick generate (come nella tua bozza)
+router.post('/quick-generate', authenticate, posterController.quickGenerate);
+
+module.exports = router;
\ No newline at end of file
diff --git a/src/routes/templates.js b/src/routes/templates.js
new file mode 100644
index 0000000..2db5de5
--- /dev/null
+++ b/src/routes/templates.js
@@ -0,0 +1,17 @@
+const express = require('express');
+const router = express.Router();
+const templateController = require('../controllers/templateController');
+const { authenticate } = require('../middleware/authenticate');
+
+// CRUD Templates
+router.post('/', authenticate, templateController.create);
+router.get('/', templateController.list);
+router.get('/types', templateController.getTypes);
+router.get('/presets', templateController.getFormatPresets);
+router.get('/:id', templateController.getById);
+router.put('/:id', authenticate, templateController.update);
+router.delete('/:id', authenticate, templateController.delete);
+router.post('/:id/duplicate', authenticate, templateController.duplicate);
+router.get('/:id/preview', templateController.getPreview);
+
+module.exports = router;
\ No newline at end of file
diff --git a/src/scripts/seedTemplates.js b/src/scripts/seedTemplates.js
new file mode 100644
index 0000000..fbd774f
--- /dev/null
+++ b/src/scripts/seedTemplates.js
@@ -0,0 +1,33 @@
+
+const Template = require('../models/Template');
+const templateSeeds = require('../templates/template-seeds');
+
+const MONGODB_URI = process.env.MONGODB_URI || '';
+
+async function seedTemplates() {
+ try {
+ // await mongoose.connect(MONGODB_URI);
+ // Opzionale: rimuovi template esistenti con stessi templateType
+ const existingTypes = templateSeeds.map(t => t.templateType);
+ await Template.deleteMany({ templateType: { $in: existingTypes } });
+ console.log('✓ Template esistenti rimossi');
+
+ // Inserisci nuovi template
+ const result = await Template.insertMany(templateSeeds);
+ console.log(`✓ ${result.length} template inseriti con successo`);
+
+ // Log dei template creati
+ result.forEach(t => {
+ console.log(` - ${t.name} (${t.templateType})`);
+ });
+
+ // await mongoose.disconnect();
+ console.log('✓ Disconnesso da MongoDB');
+ process.exit(0);
+ } catch (error) {
+ console.error('✗ Errore seeding:', error);
+ process.exit(1);
+ }
+}
+
+seedTemplates();
\ No newline at end of file
diff --git a/src/seed/seedTemplates.js b/src/seed/seedTemplates.js
index a37094c..4092d11 100644
--- a/src/seed/seedTemplates.js
+++ b/src/seed/seedTemplates.js
@@ -14,4 +14,3 @@ const seedTemplates = async () => {
};
seedTemplates();
-s
\ No newline at end of file
diff --git a/src/sendemail.js b/src/sendemail.js
index 22ef96f..43c9142 100755
--- a/src/sendemail.js
+++ b/src/sendemail.js
@@ -368,17 +368,17 @@ function checkifSendEmail() {
module.exports = {
sendEmail_base_e_manager: async function (idapp, template, to, mylocalsconf, replyTo, transport, previewonly) {
- await this.sendEmail_base(template, to, mylocalsconf, replyTo, transport, previewonly);
+ await this.sendEmail_base(idapp, template, to, mylocalsconf, replyTo, transport, previewonly);
- await this.sendEmail_base(template, tools.getAdminEmailByIdApp(idapp), mylocalsconf, '', transport, previewonly);
+ await this.sendEmail_base(idapp, template, tools.getAdminEmailByIdApp(idapp), mylocalsconf, '', transport, previewonly);
if (tools.isManagAndAdminDifferent(idapp)) {
const email = tools.getManagerEmailByIdApp(idapp);
- await this.sendEmail_base(template, email, mylocalsconf, '', transport, previewonly);
+ await this.sendEmail_base(idapp, template, email, mylocalsconf, '', transport, previewonly);
}
},
- sendEmail_base: async function (template, to, mylocalsconf, replyTo, transport, previewonly) {
+ sendEmail_base: async function (idapp, template, to, mylocalsconf, replyTo, transport, previewonly) {
if (to === '') return false;
// console.log('mylocalsconf', mylocalsconf);
@@ -389,9 +389,17 @@ module.exports = {
if (!replyTo) replyTo = '';
+ const emailSender = mylocalsconf.dataemail.from;
+ let senderName = '';
+ if (idapp) {
+ senderName = tools.getNomeAppByIdApp(mylocalsconf.idapp);
+ }
+
+ const emailcompleta = senderName ? `"${senderName}" <${emailSender}>` : emailSender;
+
const paramemail = {
message: {
- from: mylocalsconf.dataemail.from, // sender address
+ from: emailcompleta,
headers: {
'Reply-To': replyTo,
},
@@ -457,9 +465,12 @@ module.exports = {
sendEmail_Normale: async function (mylocalsconf, to, subject, html, replyTo) {
try {
+ const emailSender = tools.getEmailByIdApp(mylocalsconf.idapp);
+ const senderName = tools.getNomeAppByIdApp(mylocalsconf.idapp);
+
// setup e-mail data with unicode symbols
var mailOptions = {
- from: tools.getEmailByIdApp(mylocalsconf.idapp), // sender address
+ from: `"${senderName}" <${emailSender}>`,
dataemail: await this.getdataemail(mylocalsconf.idapp),
to: to,
generateTextFromHTML: true,
@@ -498,12 +509,12 @@ module.exports = {
try {
const reg = require('./reg/registration');
-
const idverif = reg.getlinkregByEmail(idapp, email, username);
await User.setLinkToVerifiedEmail(idapp, username, idverif);
- const strlinkreg = tools.getHostByIdApp(idapp) + process.env.CHECKREVERIF_EMAIL + `/?idapp=${idapp}&idlink=${idverif}`;
+ const strlinkreg =
+ tools.getHostByIdApp(idapp) + process.env.CHECKREVERIF_EMAIL + `/?idapp=${idapp}&idlink=${idverif}`;
return strlinkreg;
} catch (e) {
console.error('ERROR getlinkVerifyEmail');
@@ -522,15 +533,22 @@ module.exports = {
},
getLinkAbilitaCircuito: function (idapp, user, data) {
if (data.token_circuito_da_ammettere) {
- const strlink = tools.getHostByIdApp(idapp) + `/abcirc/${data.cmd}/${data.token_circuito_da_ammettere}/${user.username}/${data.myusername}`;
+ const strlink =
+ tools.getHostByIdApp(idapp) +
+ `/abcirc/${data.cmd}/${data.token_circuito_da_ammettere}/${user.username}/${data.myusername}`;
return strlink;
}
return '';
},
getPathEmail(idapp, email_template) {
- const RISO_TEMPLATES = ['reg_notifica_all_invitante', 'reg_email_benvenuto_ammesso', 'reg_chiedi_ammettere_all_invitante',
- 'circuit_chiedi_facilitatori_di_entrare', 'circuit_abilitato_al_fido_membro'];
+ const RISO_TEMPLATES = [
+ 'reg_notifica_all_invitante',
+ 'reg_email_benvenuto_ammesso',
+ 'reg_chiedi_ammettere_all_invitante',
+ 'circuit_chiedi_facilitatori_di_entrare',
+ 'circuit_abilitato_al_fido_membro',
+ ];
if (idapp === '13') {
if (RISO_TEMPLATES.includes(email_template)) {
@@ -585,34 +603,24 @@ module.exports = {
}
//Invia una email al nuovo utente
- await this.sendEmail_base(quale_email_inviare, emailto, mylocalsconf, tools.getreplyToEmailByIdApp(idapp));
+ await this.sendEmail_base(idapp, quale_email_inviare, emailto, mylocalsconf, tools.getreplyToEmailByIdApp(idapp));
if (user.verified_email && user.aportador_solidario && user.verified_by_aportador) {
const pathemail = this.getPathEmail(idapp, 'reg_notifica_all_invitante');
// Manda anche una email al suo Invitante
const recaportador = await User.getUserShortDataByUsername(idapp, user.aportador_solidario);
- const ris = await this.sendEmail_base(
- pathemail + '/' + tools.LANGADMIN,
- recaportador.email,
- mylocalsconf,
- ''
- );
+ const ris = await this.sendEmail_base(idapp, pathemail + '/' + tools.LANGADMIN, recaportador.email, mylocalsconf, '');
} else if (user.aportador_solidario && !user.verified_by_aportador) {
const pathemail = this.getPathEmail(idapp, 'reg_chiedi_ammettere_all_invitante');
// Manda una email al suo Invitante per chiedere di essere ammesso
const recaportador = await User.getUserShortDataByUsername(idapp, user.aportador_solidario);
- const ris = await this.sendEmail_base(
- pathemail + '/' + tools.LANGADMIN,
- recaportador.email,
- mylocalsconf,
- ''
- );
+ const ris = await this.sendEmail_base(idapp, pathemail + '/' + tools.LANGADMIN, recaportador.email, mylocalsconf, '');
}
// Send to the Admin an Email
- const ris = await this.sendEmail_base(
+ const ris = await this.sendEmail_base(idapp,
'admin/registration/' + tools.LANGADMIN,
tools.getAdminEmailByIdApp(idapp),
mylocalsconf,
@@ -654,7 +662,7 @@ module.exports = {
messaggioPersonalizzato: dati.messaggioPersonalizzato,
};
- const ris = await this.sendEmail_base('invitaamico/' + lang, emailto, mylocalsconf, '');
+ const ris = await this.sendEmail_base(idapp, 'invitaamico/' + lang, emailto, mylocalsconf, '');
await telegrambot.notifyToTelegram(telegrambot.phase.INVITA_AMICO, mylocalsconf);
@@ -681,7 +689,7 @@ module.exports = {
const quale_email_inviare = this.getPathEmail(idapp, 'reg_email_benvenuto_ammesso') + '/' + lang;
- const ris = await this.sendEmail_base(quale_email_inviare, emailto, mylocalsconf, '');
+ const ris = await this.sendEmail_base(idapp, quale_email_inviare, emailto, mylocalsconf, '');
await telegrambot.notifyToTelegram(telegrambot.phase.AMMETTI_UTENTE, mylocalsconf);
@@ -715,7 +723,7 @@ module.exports = {
const quale_email_inviare = this.getPathEmail(idapp, 'reg_resend_email_to_verifiyng') + '/' + lang;
- const ris = await this.sendEmail_base(quale_email_inviare, email, mylocalsconf, '');
+ const ris = await this.sendEmail_base(idapp, quale_email_inviare, email, mylocalsconf, '');
return ris;
} catch (e) {
@@ -724,6 +732,8 @@ module.exports = {
},
sendEmail_Utente_Abilitato_Circuito_FidoConcesso: async function (lang, emailto, user, idapp, dati) {
try {
+ const { Circuit } = require('../models/circuit');
+
let mylocalsconf = {
idapp,
dataemail: await this.getdataemail(idapp),
@@ -735,14 +745,15 @@ module.exports = {
usernameInvitante: dati.usernameInvitante,
linkProfiloAdmin: tools.getLinkUserProfile(idapp, dati.usernameInvitante),
user,
+ symbol: await Circuit.getSymbolByCircuitId(dati.circuitId),
usernameMembro: user.username,
nomeTerritorio: dati.nomeTerritorio,
linkTelegramTerritorio: dati.link_group,
- };
+ };
const quale_email_inviare = this.getPathEmail(idapp, 'circuit_abilitato_al_fido_membro') + '/' + lang;
- const ris = await this.sendEmail_base(quale_email_inviare, emailto, mylocalsconf, '');
+ const ris = await this.sendEmail_base(idapp, quale_email_inviare, emailto, mylocalsconf, '');
await telegrambot.notifyToTelegram(telegrambot.phase.AMMETTI_UTENTE, mylocalsconf);
@@ -751,7 +762,14 @@ module.exports = {
console.error('Err sendEmail_Utente_Ammesso', e);
}
},
- sendEmail_Richiesta_Al_Facilitatore_Di_FarEntrare_AlCircuito: async function (lang, emailto, user, userInvitante, idapp, dati) {
+ sendEmail_Richiesta_Al_Facilitatore_Di_FarEntrare_AlCircuito: async function (
+ lang,
+ emailto,
+ user,
+ userInvitante,
+ idapp,
+ dati
+ ) {
try {
// dati.circuitId
// dati.groupname
@@ -759,6 +777,8 @@ module.exports = {
const linkAbilitazione = this.getLinkAbilitaCircuito(idapp, user, dati);
+ const { Circuit } = require('../models/circuit');
+
let mylocalsconf = {
idapp,
dataemail: await this.getdataemail(idapp),
@@ -780,6 +800,7 @@ module.exports = {
comuneResidenza: user.profile.resid_str_comune,
provinciaResidenza: user.profile.resid_province,
user,
+ symbol: await Circuit.getSymbolByCircuitId(dati.circuitId),
linkAbilitazione: linkAbilitazione,
linkProfiloMembro: tools.getLinkUserProfile(idapp, user.username),
linkProfiloInvitante: tools.getLinkUserProfile(idapp, user.aportador_solidario),
@@ -789,7 +810,7 @@ module.exports = {
const quale_email_inviare = this.getPathEmail(idapp, 'circuit_chiedi_facilitatori_di_entrare') + '/' + lang;
- const ris = await this.sendEmail_base(quale_email_inviare, emailto, mylocalsconf, '');
+ const ris = await this.sendEmail_base(idapp, quale_email_inviare, emailto, mylocalsconf, '');
// await telegrambot.notifyToTelegram(telegrambot.phase.AMMETTI_UTENTE, mylocalsconf);
@@ -817,6 +838,7 @@ module.exports = {
mylocalsconf = this.setParamsForTemplate(iscritto, mylocalsconf);
await this.sendEmail_base(
+ idapp,
'iscrizione_conacreis/' + lang,
emailto,
mylocalsconf,
@@ -825,6 +847,7 @@ module.exports = {
// Send to the Admin an Email
await this.sendEmail_base(
+ idapp,
'admin/iscrizione_conacreis/' + tools.LANGADMIN,
tools.getAdminEmailByIdApp(idapp),
mylocalsconf,
@@ -845,6 +868,7 @@ module.exports = {
if (tools.isManagAndAdminDifferent(idapp)) {
await this.sendEmail_base(
+ idapp,
'admin/iscrizione_conacreis/' + tools.LANGADMIN,
tools.getManagerEmailByIdApp(idapp),
mylocalsconf,
@@ -868,7 +892,7 @@ module.exports = {
mylocalsconf = this.setParamsForTemplate(user, mylocalsconf);
- await this.sendEmail_base('resetpwd/' + lang, emailto, mylocalsconf, '');
+ await this.sendEmail_base(idapp, 'resetpwd/' + lang, emailto, mylocalsconf, '');
},
sendEmail_RisRicevuti: async function (lang, userDest, emailto, idapp, myrec, extrarec) {
@@ -895,7 +919,7 @@ module.exports = {
mylocalsconf = this.setParamsForTemplate(userDest, mylocalsconf);
- await this.sendEmail_base('risricevuti/' + lang, emailto, mylocalsconf, '');
+ await this.sendEmail_base(idapp, 'risricevuti/' + lang, emailto, mylocalsconf, '');
},
sendEmail_Booking: async function (res, lang, emailto, user, idapp, recbooking) {
@@ -933,6 +957,7 @@ module.exports = {
}
await this.sendEmail_base(
+ idapp,
'booking/' + texthtml + '/' + lang,
emailto,
mylocalsconf,
@@ -941,6 +966,7 @@ module.exports = {
// Send Email also to the Admin
await this.sendEmail_base(
+ idapp,
'admin/' + texthtml + '/' + tools.LANGADMIN,
tools.getAdminEmailByIdApp(idapp),
mylocalsconf,
@@ -949,6 +975,7 @@ module.exports = {
if (tools.isManagAndAdminDifferent(idapp)) {
await this.sendEmail_base(
+ idapp,
'admin/' + texthtml + '/' + tools.LANGADMIN,
tools.getManagerEmailByIdApp(idapp),
mylocalsconf,
@@ -1046,6 +1073,7 @@ module.exports = {
telegrambot.sendMsgTelegramToTheManagers(idapp, msgtelegram);
await this.sendEmail_base(
+ idapp,
'booking/cancelbooking/' + lang,
emailto,
mylocalsconf,
@@ -1054,6 +1082,7 @@ module.exports = {
// Send Email also to the Admin
await this.sendEmail_base(
+ idapp,
'admin/cancelbooking/' + tools.LANGADMIN,
tools.getAdminEmailByIdApp(idapp),
mylocalsconf,
@@ -1062,6 +1091,7 @@ module.exports = {
if (tools.isManagAndAdminDifferent(idapp)) {
await this.sendEmail_base(
+ idapp,
'admin/cancelbooking/' + tools.LANGADMIN,
tools.getManagerEmailByIdApp(idapp),
mylocalsconf,
@@ -1093,7 +1123,7 @@ module.exports = {
if (mylocalsconf.infoevent !== '') replyto = user.email;
else replyto = tools.getreplyToEmailByIdApp(idapp);
- return await this.sendEmail_base('msg/sendmsg/' + lang, emailto, mylocalsconf, replyto);
+ return await this.sendEmail_base(idapp, 'msg/sendmsg/' + lang, emailto, mylocalsconf, replyto);
// Send Email also to the Admin
// this.sendEmail_base('admin/sendmsg/' + lang, tools.getAdminEmailByIdApp(idapp), mylocalsconf);
@@ -1215,6 +1245,7 @@ module.exports = {
if (sendnews) {
// Send to the Admin an Email
await this.sendEmail_base(
+ idapp,
'admin/added_to_newsletter/' + tools.LANGADMIN,
tools.getAdminEmailByIdApp(idapp),
mylocalsconf,
@@ -1223,6 +1254,7 @@ module.exports = {
if (tools.isManagAndAdminDifferent(idapp)) {
await this.sendEmail_base(
+ idapp,
'admin/added_to_newsletter/' + tools.LANGADMIN,
tools.getManagerEmailByIdApp(idapp),
mylocalsconf,
@@ -1521,6 +1553,7 @@ module.exports = {
if (status !== shared_consts.OrderStatus.CANCELED && status !== shared_consts.OrderStatus.COMPLETED) {
const esito = await this.sendEmail_base(
+ idapp,
'ecommerce/' + ordertype + '/' + lang,
mylocalsconf.emailto,
mylocalsconf,
@@ -1617,6 +1650,7 @@ module.exports = {
// Send Email to the User
// console.log('-> Invio Email (', mynewsrec.numemail_sent, '/', mynewsrec.numemail_tot, ')');
const esito = await this.sendEmail_base(
+ idapp,
'newsletter/' + lang,
mylocalsconf.emailto,
mylocalsconf,
@@ -1756,6 +1790,7 @@ module.exports = {
console.log('-> Invio Email TEST a', mylocalsconf.emailto, 'previewonly', previewonly);
return await this.sendEmail_base(
+ idapp,
'newsletter/' + lang,
mylocalsconf.emailto,
mylocalsconf,
@@ -1796,6 +1831,7 @@ module.exports = {
console.log('-> Invio Email ' + mylocalsconf.subject + ' a', mylocalsconf.emailto, 'in corso...');
const risult = await this.sendEmail_base(
+ idapp,
'newsletter/' + userto.lang,
mylocalsconf.emailto,
mylocalsconf,
diff --git a/src/server/init.js b/src/server/init.js
index 57c4352..ebe38c9 100644
--- a/src/server/init.js
+++ b/src/server/init.js
@@ -120,6 +120,10 @@ async function runStartupTasks() {
await inizia();
+ if (true) {
+ // const Seed = require('../scripts/seedTemplates');
+ }
+
// 4) reset job pendenti
await resetProcessingJob();
diff --git a/src/server/setupRouters.js b/src/server/setupRouters.js
index 390728d..1e47c70 100644
--- a/src/server/setupRouters.js
+++ b/src/server/setupRouters.js
@@ -59,6 +59,10 @@ function setupRouters(app) {
});
});
+ app.get('/health', (req, res) => {
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
+ });
+
return true;
}
diff --git a/src/services/PosterEditor.js b/src/services/PosterEditor.js
new file mode 100644
index 0000000..99cef81
--- /dev/null
+++ b/src/services/PosterEditor.js
@@ -0,0 +1,151 @@
+const { createCanvas, loadImage } = require('canvas');
+
+class PosterEditor {
+ /**
+ * Crea poster con testi sovrapposti (compatibile con tua API originale)
+ */
+ async createPoster(backgroundImageUrl, data) {
+ const { titolo, data: eventDate, ora, luogo, contatti } = data;
+
+ const width = 1080;
+ const height = 1920;
+
+ const canvas = createCanvas(width, height);
+ const ctx = canvas.getContext('2d');
+
+ // Carica e disegna background
+ try {
+ let img;
+ if (backgroundImageUrl.startsWith('data:')) {
+ img = await loadImage(backgroundImageUrl);
+ } else {
+ const fetch = require('node-fetch');
+ const response = await fetch(backgroundImageUrl);
+ const buffer = await response.buffer();
+ img = await loadImage(buffer);
+ }
+
+ // Cover fit
+ const imgRatio = img.width / img.height;
+ const canvasRatio = width / height;
+
+ let dw, dh, dx, dy;
+ if (imgRatio > canvasRatio) {
+ dh = height;
+ dw = height * imgRatio;
+ dx = (width - dw) / 2;
+ dy = 0;
+ } else {
+ dw = width;
+ dh = width / imgRatio;
+ dx = 0;
+ dy = (height - dh) / 2;
+ }
+
+ ctx.drawImage(img, dx, dy, dw, dh);
+ } catch (e) {
+ // Fallback colore solido
+ ctx.fillStyle = '#1a1a2e';
+ ctx.fillRect(0, 0, width, height);
+ }
+
+ // Overlay gradient
+ const gradient = ctx.createLinearGradient(0, 0, 0, height);
+ gradient.addColorStop(0, 'rgba(0,0,0,0)');
+ gradient.addColorStop(0.5, 'rgba(0,0,0,0.3)');
+ gradient.addColorStop(1, 'rgba(0,0,0,0.85)');
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, width, height);
+
+ // Titolo
+ if (titolo) {
+ ctx.save();
+ ctx.font = 'bold 68px Arial, sans-serif';
+ ctx.fillStyle = '#ffffff';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.shadowColor = 'rgba(0,0,0,0.9)';
+ ctx.shadowBlur = 20;
+ ctx.shadowOffsetX = 3;
+ ctx.shadowOffsetY = 3;
+
+ // Word wrap manuale
+ const maxWidth = width * 0.9;
+ const lines = this._wrapText(ctx, titolo.toUpperCase(), maxWidth);
+ const lineHeight = 80;
+ const startY = height * 0.50 - ((lines.length - 1) * lineHeight) / 2;
+
+ lines.forEach((line, i) => {
+ ctx.fillText(line, width / 2, startY + i * lineHeight);
+ });
+
+ ctx.restore();
+ }
+
+ // Data e ora
+ if (eventDate) {
+ ctx.save();
+ ctx.font = 'bold 44px Arial, sans-serif';
+ ctx.fillStyle = '#ffd700';
+ ctx.textAlign = 'center';
+ ctx.shadowColor = 'rgba(0,0,0,0.8)';
+ ctx.shadowBlur = 10;
+
+ const dateText = ora ? `${eventDate} • ORE ${ora}` : eventDate;
+ ctx.fillText(dateText.toUpperCase(), width / 2, height * 0.68);
+ ctx.restore();
+ }
+
+ // Luogo
+ if (luogo) {
+ ctx.save();
+ ctx.font = '600 30px Arial, sans-serif';
+ ctx.fillStyle = '#ffffff';
+ ctx.textAlign = 'center';
+ ctx.fillText(`📍 ${luogo}`, width / 2, height * 0.76);
+ ctx.restore();
+ }
+
+ // Contatti
+ if (contatti) {
+ ctx.save();
+ ctx.font = '400 24px Arial, sans-serif';
+ ctx.fillStyle = '#cccccc';
+ ctx.textAlign = 'center';
+ ctx.fillText(contatti, width / 2, height * 0.85);
+ ctx.restore();
+ }
+
+ // Ritorna base64
+ return canvas.toDataURL('image/png');
+ }
+
+ /**
+ * Word wrap utility
+ */
+ _wrapText(ctx, text, maxWidth) {
+ const words = text.split(' ');
+ const lines = [];
+ let currentLine = '';
+
+ words.forEach(word => {
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
+ const metrics = ctx.measureText(testLine);
+
+ if (metrics.width > maxWidth && currentLine) {
+ lines.push(currentLine);
+ currentLine = word;
+ } else {
+ currentLine = testLine;
+ }
+ });
+
+ if (currentLine) {
+ lines.push(currentLine);
+ }
+
+ return lines;
+ }
+}
+
+module.exports = new PosterEditor();
\ No newline at end of file
diff --git a/src/services/imageGenerator.js b/src/services/imageGenerator.js
new file mode 100644
index 0000000..2a5e520
--- /dev/null
+++ b/src/services/imageGenerator.js
@@ -0,0 +1,154 @@
+const fetch = require('node-fetch');
+
+class ImageGenerator {
+ constructor() {
+ this.falKey = process.env.FAL_KEY;
+ this.hfToken = process.env.HF_TOKEN;
+ this.ideogramKey = process.env.IDEOGRAM_KEY;
+ }
+
+ async generate(provider, prompt, options = {}) {
+ const {
+ negativePrompt,
+ aspectRatio = '9:16',
+ model,
+ seed,
+ steps,
+ cfg
+ } = options;
+
+ switch (provider) {
+ case 'ideogram':
+ return this._generateIdeogram(prompt, { aspectRatio });
+ case 'fal':
+ return this._generateFal(prompt, { aspectRatio, seed, steps, cfg });
+ case 'hf':
+ default:
+ return this._generateHuggingFace(prompt, { negativePrompt });
+ }
+ }
+
+ // Ideogram V2 (via Fal.ai) - Ottimo per testo
+ async _generateIdeogram(prompt, options = {}) {
+ console.log('--- Generazione Ideogram V2 ---');
+
+ const response = await fetch('https://fal.run/fal-ai/ideogram/v2', {
+ method: 'POST',
+ headers: {
+ Authorization: `Key ${this.falKey}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ prompt,
+ aspect_ratio: options.aspectRatio || '9:16',
+ style_type: 'DESIGN',
+ expand_prompt: true
+ }),
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`Ideogram error: ${response.status} - ${errorText}`);
+ }
+
+ const result = await response.json();
+ const imageUrl = result.images?.[0]?.url;
+
+ if (!imageUrl) throw new Error('Ideogram: nessun URL restituito');
+ return imageUrl;
+ }
+
+ // Flux Dev (via Fal.ai)
+ async _generateFal(prompt, options = {}) {
+ console.log('--- Generazione Fal Flux Dev ---');
+
+ const imageSizeMap = {
+ '9:16': 'portrait_16_9',
+ '16:9': 'landscape_16_9',
+ '1:1': 'square'
+ };
+
+ const response = await fetch('https://fal.run/fal-ai/flux/dev', {
+ method: 'POST',
+ headers: {
+ Authorization: `Key ${this.falKey}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ prompt,
+ image_size: imageSizeMap[options.aspectRatio] || 'portrait_16_9',
+ num_images: 1,
+ enable_safety_checker: false,
+ seed: options.seed,
+ num_inference_steps: options.steps || 28,
+ guidance_scale: options.cfg || 7.5
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Fal error: ${response.status}`);
+ }
+
+ const result = await response.json();
+ return result.images?.[0]?.url;
+ }
+
+ // Flux Dev (via Hugging Face) - GRATIS
+ async _generateHuggingFace(prompt, options = {}) {
+ console.log('--- Generazione HuggingFace (Gratis) ---');
+
+ const response = await fetch(
+ 'https://router.huggingface.co/hf-inference/models/black-forest-labs/FLUX.1-dev',
+ {
+ headers: {
+ Authorization: `Bearer ${this.hfToken}`,
+ 'Content-Type': 'application/json',
+ },
+ method: 'POST',
+ body: JSON.stringify({
+ inputs: prompt,
+ parameters: {
+ negative_prompt: options.negativePrompt
+ }
+ }),
+ }
+ );
+
+ if (!response.ok) {
+ const err = await response.text();
+ throw new Error(`HuggingFace error: ${response.status} - ${err}`);
+ }
+
+ const arrayBuffer = await response.arrayBuffer();
+ const buffer = Buffer.from(arrayBuffer);
+ return `data:image/jpeg;base64,${buffer.toString('base64')}`;
+ }
+
+ // Utility: verifica disponibilità provider
+ async checkProvider(provider) {
+ const checks = {
+ hf: !!this.hfToken,
+ fal: !!this.falKey,
+ ideogram: !!this.falKey // Ideogram via Fal
+ };
+
+ return checks[provider] || false;
+ }
+
+ // Lista provider disponibili
+ getAvailableProviders() {
+ const providers = [];
+
+ if (this.hfToken) {
+ providers.push({ id: 'hf', name: 'HuggingFace (Gratis)', cost: 'free' });
+ }
+ if (this.falKey) {
+ providers.push({ id: 'fal', name: 'Fal Flux Dev', cost: 'paid' });
+ providers.push({ id: 'ideogram', name: 'Ideogram V2', cost: 'paid' });
+ }
+
+ return providers;
+ }
+}
+
+module.exports = new ImageGenerator();
\ No newline at end of file
diff --git a/src/services/posterRenderer.js b/src/services/posterRenderer.js
new file mode 100644
index 0000000..7533ea2
--- /dev/null
+++ b/src/services/posterRenderer.js
@@ -0,0 +1,870 @@
+
+const { createCanvas, loadImage, registerFont } = require('canvas');
+const fs = require('fs').promises;
+const path = require('path');
+const sharp = require('sharp');
+
+// Registra font personalizzati
+const FONTS_DIR = process.env.FONTS_DIR || './fonts';
+
+const registerFonts = async () => {
+ const fontMappings = [
+ { file: 'Montserrat-Black.ttf', family: 'Montserrat', weight: '900' },
+ { file: 'Montserrat-Bold.ttf', family: 'Montserrat', weight: '700' },
+ { file: 'Montserrat-Regular.ttf', family: 'Montserrat', weight: '400' },
+ { file: 'BebasNeue-Regular.ttf', family: 'Bebas Neue', weight: '400' },
+ { file: 'OpenSans-Bold.ttf', family: 'Open Sans', weight: '700' },
+ { file: 'OpenSans-SemiBold.ttf', family: 'Open Sans', weight: '600' },
+ { file: 'OpenSans-Regular.ttf', family: 'Open Sans', weight: '400' },
+ { file: 'OpenSans-Light.ttf', family: 'Open Sans', weight: '300' },
+ { file: 'PlayfairDisplay-Bold.ttf', family: 'Playfair Display', weight: '700' },
+ { file: 'PlayfairDisplay-Regular.ttf', family: 'Playfair Display', weight: '400' }
+ ];
+
+ for (const font of fontMappings) {
+ const fontPath = path.join(FONTS_DIR, font.file);
+ try {
+ await fs.access(fontPath);
+ registerFont(fontPath, { family: font.family, weight: font.weight });
+ } catch (e) {
+ // Font non trovato, usa fallback
+ }
+ }
+};
+
+// Inizializza fonts al caricamento modulo
+registerFonts().catch(console.warn);
+
+class PosterRenderer {
+ constructor() {
+ this.version = '1.0.0';
+ }
+
+ /**
+ * Render principale
+ */
+ async render(options) {
+ const {
+ template,
+ content,
+ assets,
+ layerOverrides = {},
+ outputDir,
+ posterId
+ } = options;
+
+ const startTime = Date.now();
+
+ // Dimensioni canvas
+ const width = template.format?.width || 2480;
+ const height = template.format?.height || 3508;
+
+ // Crea canvas
+ const canvas = createCanvas(width, height);
+ const ctx = canvas.getContext('2d');
+
+ // 1. Disegna background
+ await this._drawBackground(ctx, template, assets, width, height);
+
+ // 2. Ordina layer per zIndex
+ const sortedLayers = [...(template.layers || [])]
+ .sort((a, b) => (a.zIndex || 0) - (b.zIndex || 0));
+
+ // 3. Disegna ogni layer
+ for (const layer of sortedLayers) {
+ if (layer.visible === false) continue;
+
+ const override = layerOverrides[layer.id] || {};
+ const mergedLayer = this._mergeLayerOverride(layer, override);
+
+ await this._drawLayer(ctx, mergedLayer, content, assets, width, height, template);
+ }
+
+ // 4. Disegna loghi
+ if (template.logoSlots?.enabled && assets?.logos?.length > 0) {
+ await this._drawLogos(ctx, template.logoSlots, assets.logos, width, height);
+ }
+
+ // 5. Salva output
+ await fs.mkdir(outputDir, { recursive: true });
+
+ const baseName = `poster_${posterId}_${Date.now()}`;
+ const pngPath = path.join(outputDir, `${baseName}.png`);
+ const jpgPath = path.join(outputDir, `${baseName}.jpg`);
+
+ // Salva PNG
+ const pngBuffer = canvas.toBuffer('image/png');
+ await fs.writeFile(pngPath, pngBuffer);
+
+ // Salva JPG con Sharp (migliore qualità)
+ await sharp(pngBuffer)
+ .jpeg({ quality: 95, progressive: true })
+ .toFile(jpgPath);
+
+ const [pngStats, jpgStats] = await Promise.all([
+ fs.stat(pngPath),
+ fs.stat(jpgPath)
+ ]);
+
+ return {
+ pngPath,
+ jpgPath,
+ pngSize: pngStats.size,
+ jpgSize: jpgStats.size,
+ dimensions: { width, height },
+ duration: Date.now() - startTime,
+ engineVersion: this.version
+ };
+ }
+
+ /**
+ * Disegna background
+ */
+ async _drawBackground(ctx, template, assets, width, height) {
+ // Colore di sfondo base
+ ctx.fillStyle = template.backgroundColor || '#1a1a2e';
+ ctx.fillRect(0, 0, width, height);
+
+ // Background image
+ const bgAsset = assets?.backgroundImage;
+ const bgLayer = template.layers?.find(l => l.type === 'backgroundImage');
+
+ if (bgAsset?.url) {
+ try {
+ const img = await this._loadImageFromUrl(bgAsset.url);
+
+ // Calcola dimensioni per cover
+ const imgRatio = img.width / img.height;
+ const canvasRatio = width / height;
+
+ let drawWidth, drawHeight, drawX, drawY;
+
+ if (imgRatio > canvasRatio) {
+ drawHeight = height;
+ drawWidth = height * imgRatio;
+ drawX = (width - drawWidth) / 2;
+ drawY = 0;
+ } else {
+ drawWidth = width;
+ drawHeight = width / imgRatio;
+ drawX = 0;
+ drawY = (height - drawHeight) / 2;
+ }
+
+ // Applica blur se definito
+ if (bgLayer?.style?.blur > 0) {
+ ctx.filter = `blur(${bgLayer.style.blur}px)`;
+ }
+
+ ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
+ ctx.filter = 'none';
+
+ // Applica overlay gradient
+ const overlay = bgLayer?.style?.overlay;
+ if (overlay?.enabled) {
+ this._drawOverlay(ctx, overlay, width, height);
+ }
+ } catch (e) {
+ console.warn('Background image load failed:', e.message);
+ // Usa fallback
+ if (bgLayer?.fallback) {
+ this._drawFallback(ctx, bgLayer.fallback, width, height);
+ }
+ }
+ } else if (bgLayer?.fallback) {
+ this._drawFallback(ctx, bgLayer.fallback, width, height);
+ }
+ }
+
+ /**
+ * Disegna overlay gradient
+ */
+ _drawOverlay(ctx, overlay, width, height) {
+ if (overlay.type === 'solid') {
+ ctx.fillStyle = overlay.color || 'rgba(0,0,0,0.5)';
+ ctx.fillRect(0, 0, width, height);
+ return;
+ }
+
+ // Gradient
+ let gradient;
+ const dir = overlay.direction || 'to-bottom';
+
+ if (dir === 'to-bottom') {
+ gradient = ctx.createLinearGradient(0, 0, 0, height);
+ } else if (dir === 'to-top') {
+ gradient = ctx.createLinearGradient(0, height, 0, 0);
+ } else if (dir === 'to-right') {
+ gradient = ctx.createLinearGradient(0, 0, width, 0);
+ } else if (dir === 'to-left') {
+ gradient = ctx.createLinearGradient(width, 0, 0, 0);
+ } else if (dir === 'to-bottom-right') {
+ gradient = ctx.createLinearGradient(0, 0, width, height);
+ } else {
+ gradient = ctx.createLinearGradient(0, 0, 0, height);
+ }
+
+ if (overlay.stops) {
+ overlay.stops.forEach(stop => {
+ gradient.addColorStop(stop.position, stop.color);
+ });
+ } else {
+ gradient.addColorStop(0, 'rgba(0,0,0,0)');
+ gradient.addColorStop(1, 'rgba(0,0,0,0.7)');
+ }
+
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, width, height);
+ }
+
+ /**
+ * Disegna fallback
+ */
+ _drawFallback(ctx, fallback, width, height) {
+ if (fallback.type === 'solid') {
+ ctx.fillStyle = fallback.color || '#333333';
+ ctx.fillRect(0, 0, width, height);
+ } else if (fallback.type === 'gradient' && fallback.colors) {
+ const gradient = ctx.createLinearGradient(0, 0, 0, height);
+ fallback.colors.forEach((color, i) => {
+ gradient.addColorStop(i / (fallback.colors.length - 1), color);
+ });
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, width, height);
+ }
+ }
+
+ /**
+ * Disegna singolo layer
+ */
+ async _drawLayer(ctx, layer, content, assets, canvasWidth, canvasHeight, template) {
+ const pos = this._calculatePosition(layer.position, layer.anchor, canvasWidth, canvasHeight);
+
+ switch (layer.type) {
+ case 'backgroundImage':
+ // Già gestito in _drawBackground
+ break;
+
+ case 'mainImage':
+ await this._drawMainImage(ctx, assets?.mainImage, pos, layer.style);
+ break;
+
+ case 'title':
+ this._drawText(ctx, content?.title, pos, layer.style, template.palette);
+ break;
+
+ case 'subtitle':
+ this._drawText(ctx, content?.subtitle, pos, layer.style, template.palette);
+ break;
+
+ case 'eventDate':
+ const dateText = content?.eventTime
+ ? `${content.eventDate} • ${content.eventTime}`
+ : content?.eventDate;
+ this._drawText(ctx, dateText, pos, layer.style, template.palette);
+ break;
+
+ case 'eventTime':
+ this._drawText(ctx, content?.eventTime, pos, layer.style, template.palette);
+ break;
+
+ case 'location':
+ this._drawTextWithIcon(ctx, content?.location, pos, layer, template.palette);
+ break;
+
+ case 'contacts':
+ this._drawText(ctx, content?.contacts, pos, layer.style, template.palette);
+ break;
+
+ case 'extraText':
+ const extraTexts = Array.isArray(content?.extraText)
+ ? content.extraText.join(' • ')
+ : content?.extraText;
+ this._drawText(ctx, extraTexts, pos, layer.style, template.palette);
+ break;
+
+ case 'customText':
+ const customValue = content?.customFields?.get(layer.id);
+ this._drawText(ctx, customValue, pos, layer.style, template.palette);
+ break;
+
+ case 'divider':
+ this._drawDivider(ctx, pos, layer.style);
+ break;
+
+ case 'shape':
+ this._drawShape(ctx, pos, layer.style);
+ break;
+
+ default:
+ console.warn(`Layer type non gestito: ${layer.type}`);
+ }
+ }
+
+ /**
+ * Calcola posizione assoluta da coordinate relative
+ */
+ _calculatePosition(position, anchor, canvasWidth, canvasHeight) {
+ const relX = position.x || 0;
+ const relY = position.y || 0;
+ const relW = position.w || 1;
+ const relH = position.h || 0.1;
+
+ const absW = relW * canvasWidth;
+ const absH = relH * canvasHeight;
+
+ let absX = relX * canvasWidth;
+ let absY = relY * canvasHeight;
+
+ // Aggiusta per anchor
+ switch (anchor) {
+ case 'top-center':
+ absX -= absW / 2;
+ break;
+ case 'top-right':
+ absX -= absW;
+ break;
+ case 'center-left':
+ absY -= absH / 2;
+ break;
+ case 'center':
+ absX -= absW / 2;
+ absY -= absH / 2;
+ break;
+ case 'center-right':
+ absX -= absW;
+ absY -= absH / 2;
+ break;
+ case 'bottom-left':
+ absY -= absH;
+ break;
+ case 'bottom-center':
+ absX -= absW / 2;
+ absY -= absH;
+ break;
+ case 'bottom-right':
+ absX -= absW;
+ absY -= absH;
+ break;
+ // top-left è default, nessun aggiustamento
+ }
+
+ return { x: absX, y: absY, w: absW, h: absH };
+ }
+
+ /**
+ * Disegna main image
+ */
+ async _drawMainImage(ctx, asset, pos, style = {}) {
+ if (!asset?.url) return;
+
+ try {
+ const img = await this._loadImageFromUrl(asset.url);
+
+ ctx.save();
+
+ // Border radius (clip)
+ const radius = style.borderRadius || 0;
+ if (radius > 0) {
+ this._roundRect(ctx, pos.x, pos.y, pos.w, pos.h, radius);
+ ctx.clip();
+ }
+
+ // Shadow
+ if (style.shadow?.enabled) {
+ ctx.shadowColor = style.shadow.color || 'rgba(0,0,0,0.5)';
+ ctx.shadowBlur = style.shadow.blur || 20;
+ ctx.shadowOffsetX = style.shadow.offsetX || 0;
+ ctx.shadowOffsetY = style.shadow.offsetY || 10;
+ }
+
+ // Calcola dimensioni per object-fit
+ const { sx, sy, sw, sh, dx, dy, dw, dh } = this._calculateObjectFit(
+ img.width, img.height, pos.w, pos.h, style.objectFit || 'cover'
+ );
+
+ ctx.drawImage(img, sx, sy, sw, sh, pos.x + dx, pos.y + dy, dw, dh);
+
+ // Border
+ if (style.border?.enabled) {
+ ctx.strokeStyle = style.border.color || '#ffffff';
+ ctx.lineWidth = style.border.width || 2;
+ if (radius > 0) {
+ this._roundRect(ctx, pos.x, pos.y, pos.w, pos.h, radius);
+ } else {
+ ctx.strokeRect(pos.x, pos.y, pos.w, pos.h);
+ }
+ ctx.stroke();
+ }
+
+ ctx.restore();
+ } catch (e) {
+ console.warn('Main image load failed:', e.message);
+ }
+ }
+
+ /**
+ * Calcola object-fit
+ */
+ _calculateObjectFit(imgW, imgH, boxW, boxH, fit) {
+ let sx = 0, sy = 0, sw = imgW, sh = imgH;
+ let dx = 0, dy = 0, dw = boxW, dh = boxH;
+
+ const imgRatio = imgW / imgH;
+ const boxRatio = boxW / boxH;
+
+ if (fit === 'cover') {
+ if (imgRatio > boxRatio) {
+ sw = imgH * boxRatio;
+ sx = (imgW - sw) / 2;
+ } else {
+ sh = imgW / boxRatio;
+ sy = (imgH - sh) / 2;
+ }
+ } else if (fit === 'contain') {
+ if (imgRatio > boxRatio) {
+ dh = boxW / imgRatio;
+ dy = (boxH - dh) / 2;
+ } else {
+ dw = boxH * imgRatio;
+ dx = (boxW - dw) / 2;
+ }
+ }
+ // 'fill' usa valori default
+
+ return { sx, sy, sw, sh, dx, dy, dw, dh };
+ }
+
+ /**
+ * Disegna testo
+ */
+ _drawText(ctx, text, pos, style = {}, palette = {}) {
+ if (!text) return;
+
+ ctx.save();
+
+ // Font
+ const fontWeight = style.fontWeight || 400;
+ const fontSize = style.fontSize || 48;
+ const fontFamily = style.fontFamily || 'Open Sans';
+ ctx.font = `${fontWeight} ${fontSize}px "${fontFamily}"`;
+
+ // Colore
+ ctx.fillStyle = style.color || palette.text || '#ffffff';
+
+ // Allineamento
+ const align = style.textAlign || 'center';
+ ctx.textAlign = align;
+ ctx.textBaseline = 'middle';
+
+ // Transform
+ let displayText = text;
+ if (style.textTransform === 'uppercase') {
+ displayText = text.toUpperCase();
+ } else if (style.textTransform === 'lowercase') {
+ displayText = text.toLowerCase();
+ } else if (style.textTransform === 'capitalize') {
+ displayText = text.replace(/\b\w/g, c => c.toUpperCase());
+ }
+
+ // Calcola X in base ad allineamento
+ let textX;
+ if (align === 'center') {
+ textX = pos.x + pos.w / 2;
+ } else if (align === 'right') {
+ textX = pos.x + pos.w;
+ } else {
+ textX = pos.x;
+ }
+
+ const textY = pos.y + pos.h / 2;
+
+ // Letter spacing (manuale)
+ if (style.letterSpacing && style.letterSpacing > 0) {
+ this._drawTextWithSpacing(ctx, displayText, textX, textY, style, pos);
+ } else {
+ // Shadow
+ if (style.shadow?.enabled) {
+ ctx.shadowColor = style.shadow.color || 'rgba(0,0,0,0.8)';
+ ctx.shadowBlur = style.shadow.blur || 10;
+ ctx.shadowOffsetX = style.shadow.offsetX || 2;
+ ctx.shadowOffsetY = style.shadow.offsetY || 2;
+ }
+
+ // Stroke
+ if (style.stroke?.enabled) {
+ ctx.strokeStyle = style.stroke.color || 'rgba(0,0,0,0.5)';
+ ctx.lineWidth = style.stroke.width || 2;
+ ctx.strokeText(displayText, textX, textY);
+ }
+
+ // Fill
+ ctx.fillText(displayText, textX, textY);
+ }
+
+ ctx.restore();
+ }
+
+ /**
+ * Disegna testo con letter-spacing
+ */
+ _drawTextWithSpacing(ctx, text, x, y, style, pos) {
+ const spacing = style.letterSpacing || 0;
+ const chars = text.split('');
+
+ // Calcola larghezza totale
+ let totalWidth = 0;
+ chars.forEach(char => {
+ totalWidth += ctx.measureText(char).width + spacing;
+ });
+ totalWidth -= spacing; // Rimuovi ultimo spacing
+
+ // Calcola startX in base ad allineamento
+ let startX;
+ if (style.textAlign === 'center') {
+ startX = x - totalWidth / 2;
+ } else if (style.textAlign === 'right') {
+ startX = x - totalWidth;
+ } else {
+ startX = x;
+ }
+
+ // Shadow
+ if (style.shadow?.enabled) {
+ ctx.shadowColor = style.shadow.color || 'rgba(0,0,0,0.8)';
+ ctx.shadowBlur = style.shadow.blur || 10;
+ ctx.shadowOffsetX = style.shadow.offsetX || 2;
+ ctx.shadowOffsetY = style.shadow.offsetY || 2;
+ }
+
+ // Disegna ogni carattere
+ ctx.textAlign = 'left';
+ let currentX = startX;
+
+ chars.forEach(char => {
+ if (style.stroke?.enabled) {
+ ctx.strokeStyle = style.stroke.color || 'rgba(0,0,0,0.5)';
+ ctx.lineWidth = style.stroke.width || 2;
+ ctx.strokeText(char, currentX, y);
+ }
+ ctx.fillText(char, currentX, y);
+ currentX += ctx.measureText(char).width + spacing;
+ });
+ }
+
+ /**
+ * Disegna testo con icona
+ */
+ _drawTextWithIcon(ctx, text, pos, layer, palette) {
+ if (!text) return;
+
+ const icon = layer.icon;
+ const style = layer.style || {};
+
+ // Se icona abilitata, disegna simbolo prima del testo
+ if (icon?.enabled) {
+ ctx.save();
+
+ const iconSize = icon.size || 24;
+ const iconColor = icon.color || palette?.accent || '#e74c3c';
+
+ // Disegna simbolo location semplificato
+ ctx.fillStyle = iconColor;
+ ctx.font = `${iconSize}px Arial`;
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+
+ const iconChar = '📍'; // Emoji o usa font icon
+ const textWithIcon = `${iconChar} ${text}`;
+
+ // Ora disegna testo normale con icona
+ this._drawText(ctx, textWithIcon, pos, style, palette);
+
+ ctx.restore();
+ } else {
+ this._drawText(ctx, text, pos, style, palette);
+ }
+ }
+
+ /**
+ * Disegna loghi
+ */
+ async _drawLogos(ctx, logoSlots, logos, canvasWidth, canvasHeight) {
+ const slots = logoSlots.slots || [];
+ const maxCount = Math.min(logos.length, logoSlots.maxCount || 3, slots.length);
+
+ for (let i = 0; i < maxCount; i++) {
+ const logo = logos[i];
+ const slot = slots[i];
+
+ if (!logo?.url || !slot) continue;
+
+ try {
+ const img = await this._loadImageFromUrl(logo.url);
+ const pos = this._calculatePosition(slot.position, slot.anchor, canvasWidth, canvasHeight);
+
+ ctx.save();
+
+ // Opacity
+ ctx.globalAlpha = slot.style?.opacity ?? 0.9;
+
+ // Object fit contain per loghi
+ const { sx, sy, sw, sh, dx, dy, dw, dh } = this._calculateObjectFit(
+ img.width, img.height, pos.w, pos.h, 'contain'
+ );
+
+ ctx.drawImage(img, sx, sy, sw, sh, pos.x + dx, pos.y + dy, dw, dh);
+
+ ctx.restore();
+ } catch (e) {
+ console.warn(`Logo ${i} load failed:`, e.message);
+ }
+ }
+ }
+
+ /**
+ * Disegna divider
+ */
+ _drawDivider(ctx, pos, style = {}) {
+ ctx.save();
+
+ ctx.strokeStyle = style.color || '#ffffff';
+ ctx.lineWidth = style.width || 2;
+ ctx.globalAlpha = style.opacity || 0.5;
+
+ ctx.beginPath();
+ ctx.moveTo(pos.x, pos.y + pos.h / 2);
+ ctx.lineTo(pos.x + pos.w, pos.y + pos.h / 2);
+ ctx.stroke();
+
+ ctx.restore();
+ }
+
+ /**
+ * Disegna shape
+ */
+ _drawShape(ctx, pos, style = {}) {
+ ctx.save();
+
+ ctx.fillStyle = style.fill || 'rgba(255,255,255,0.1)';
+ ctx.strokeStyle = style.stroke || 'transparent';
+ ctx.lineWidth = style.strokeWidth || 0;
+ ctx.globalAlpha = style.opacity || 1;
+
+ const shape = style.shape || 'rectangle';
+ const radius = style.borderRadius || 0;
+
+ if (shape === 'rectangle') {
+ if (radius > 0) {
+ this._roundRect(ctx, pos.x, pos.y, pos.w, pos.h, radius);
+ ctx.fill();
+ if (style.strokeWidth) ctx.stroke();
+ } else {
+ ctx.fillRect(pos.x, pos.y, pos.w, pos.h);
+ if (style.strokeWidth) ctx.strokeRect(pos.x, pos.y, pos.w, pos.h);
+ }
+ } else if (shape === 'circle' || shape === 'ellipse') {
+ ctx.beginPath();
+ ctx.ellipse(
+ pos.x + pos.w / 2,
+ pos.y + pos.h / 2,
+ pos.w / 2,
+ pos.h / 2,
+ 0, 0, Math.PI * 2
+ );
+ ctx.fill();
+ if (style.strokeWidth) ctx.stroke();
+ }
+
+ ctx.restore();
+ }
+
+ /**
+ * Utility: rounded rectangle
+ */
+ _roundRect(ctx, x, y, w, h, radius) {
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ ctx.lineTo(x + w - radius, y);
+ ctx.quadraticCurveTo(x + w, y, x + w, y + radius);
+ ctx.lineTo(x + w, y + h - radius);
+ ctx.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
+ ctx.lineTo(x + radius, y + h);
+ ctx.quadraticCurveTo(x, y + h, x, y + h - radius);
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+ }
+
+ /**
+ * Utility: carica immagine da URL o path locale
+ */
+ async _loadImageFromUrl(url) {
+ if (!url) throw new Error('URL mancante');
+
+ // Base64
+ if (url.startsWith('data:')) {
+ return loadImage(url);
+ }
+
+ // Path locale
+ if (url.startsWith('/uploads') || url.startsWith('./uploads')) {
+ const localPath = url.startsWith('/')
+ ? path.join(process.cwd(), url)
+ : url;
+ return loadImage(localPath);
+ }
+
+ // URL remoto
+ if (url.startsWith('http://') || url.startsWith('https://')) {
+ const fetch = require('node-fetch');
+ const response = await fetch(url);
+ const buffer = await response.buffer();
+ return loadImage(buffer);
+ }
+
+ // Assume path locale
+ return loadImage(url);
+ }
+
+ /**
+ * Merge layer con override
+ */
+ _mergeLayerOverride(layer, override) {
+ if (!override || Object.keys(override).length === 0) {
+ return layer;
+ }
+
+ return {
+ ...layer,
+ position: override.position ? { ...layer.position, ...override.position } : layer.position,
+ visible: override.visible !== undefined ? override.visible : layer.visible,
+ style: override.style ? { ...layer.style, ...override.style } : layer.style
+ };
+ }
+
+ /**
+ * Quick render (semplificato per quick-generate)
+ */
+ async quickRender(options) {
+ const {
+ backgroundUrl,
+ content,
+ outputPath,
+ width = 1080,
+ height = 1920
+ } = options;
+
+ const canvas = createCanvas(width, height);
+ const ctx = canvas.getContext('2d');
+
+ // Background
+ if (backgroundUrl) {
+ try {
+ const img = await this._loadImageFromUrl(backgroundUrl);
+ const imgRatio = img.width / img.height;
+ const canvasRatio = width / height;
+
+ let dw, dh, dx, dy;
+ if (imgRatio > canvasRatio) {
+ dh = height;
+ dw = height * imgRatio;
+ dx = (width - dw) / 2;
+ dy = 0;
+ } else {
+ dw = width;
+ dh = width / imgRatio;
+ dx = 0;
+ dy = (height - dh) / 2;
+ }
+
+ ctx.drawImage(img, dx, dy, dw, dh);
+ } catch (e) {
+ ctx.fillStyle = '#1a1a2e';
+ ctx.fillRect(0, 0, width, height);
+ }
+ }
+
+ // Overlay gradient
+ const gradient = ctx.createLinearGradient(0, 0, 0, height);
+ gradient.addColorStop(0, 'rgba(0,0,0,0)');
+ gradient.addColorStop(0.4, 'rgba(0,0,0,0.2)');
+ gradient.addColorStop(0.7, 'rgba(0,0,0,0.6)');
+ gradient.addColorStop(1, 'rgba(0,0,0,0.85)');
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, width, height);
+
+ // Title
+ if (content.title) {
+ ctx.save();
+ ctx.font = 'bold 72px "Montserrat", sans-serif';
+ ctx.fillStyle = '#ffffff';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.shadowColor = 'rgba(0,0,0,0.8)';
+ ctx.shadowBlur = 15;
+ ctx.shadowOffsetY = 4;
+ ctx.fillText(content.title.toUpperCase(), width / 2, height * 0.52);
+ ctx.restore();
+ }
+
+ // Subtitle
+ if (content.subtitle) {
+ ctx.save();
+ ctx.font = '400 32px "Open Sans", sans-serif';
+ ctx.fillStyle = '#f0f0f0';
+ ctx.textAlign = 'center';
+ ctx.shadowColor = 'rgba(0,0,0,0.6)';
+ ctx.shadowBlur = 8;
+ ctx.fillText(content.subtitle, width / 2, height * 0.60);
+ ctx.restore();
+ }
+
+ // Date
+ if (content.eventDate) {
+ ctx.save();
+ ctx.font = '400 48px "Bebas Neue", sans-serif';
+ ctx.fillStyle = '#ffd700';
+ ctx.textAlign = 'center';
+ ctx.shadowColor = 'rgba(0,0,0,0.8)';
+ ctx.shadowBlur = 10;
+ const dateText = content.eventTime
+ ? `${content.eventDate} • ORE ${content.eventTime}`
+ : content.eventDate;
+ ctx.fillText(dateText.toUpperCase(), width / 2, height * 0.70);
+ ctx.restore();
+ }
+
+ // Location
+ if (content.location) {
+ ctx.save();
+ ctx.font = '600 28px "Open Sans", sans-serif';
+ ctx.fillStyle = '#ffffff';
+ ctx.textAlign = 'center';
+ ctx.fillText(`📍 ${content.location}`, width / 2, height * 0.78);
+ ctx.restore();
+ }
+
+ // Contacts
+ if (content.contacts) {
+ ctx.save();
+ ctx.font = '400 22px "Open Sans", sans-serif';
+ ctx.fillStyle = '#cccccc';
+ ctx.textAlign = 'center';
+ ctx.fillText(content.contacts, width / 2, height * 0.86);
+ ctx.restore();
+ }
+
+ // Salva
+ const buffer = canvas.toBuffer('image/png');
+ await fs.writeFile(outputPath, buffer);
+
+ return {
+ path: outputPath,
+ size: buffer.length,
+ dimensions: { width, height }
+ };
+ }
+}
+
+module.exports = new PosterRenderer();
\ No newline at end of file
diff --git a/src/telegram/telegrambot.js b/src/telegram/telegrambot.js
index debc703..b3b2cf2 100644
--- a/src/telegram/telegrambot.js
+++ b/src/telegram/telegrambot.js
@@ -1048,6 +1048,7 @@ const MyTelegramBot = {
token_circuito_da_ammettere: token,
nomeTerritorio: mycircuit.name,
myusername: userDest,
+ circuitId: mycircuit._id,
};
// if (usersmanagers) {
// for (const recadminCirc of usersmanagers) {
diff --git a/src/templates/template-seeds.js b/src/templates/template-seeds.js
new file mode 100644
index 0000000..e4e5601
--- /dev/null
+++ b/src/templates/template-seeds.js
@@ -0,0 +1,2353 @@
+const templateSeeds = [
+ // ============================================
+ // 1. RACCOLTA FUNGHI
+ // ============================================
+ {
+ name: 'Raccolta Funghi nel Bosco',
+ templateType: 'outdoor-nature',
+ description: 'Template per eventi di raccolta funghi, passeggiate naturalistiche, sagre micologiche',
+
+ format: {
+ preset: 'instagram-story',
+ width: 1080,
+ height: 1920,
+ unit: 'px',
+ dpi: 72,
+ },
+
+ safeArea: { top: 0.03, right: 0.04, bottom: 0.03, left: 0.04 },
+ backgroundColor: '#1a150f',
+
+ layers: [
+ {
+ id: 'layer_bg',
+ type: 'backgroundImage',
+ zIndex: 0,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ required: false,
+ fallback: {
+ type: 'gradient',
+ direction: 'to-bottom',
+ colors: ['#2d3a1f', '#1a150f', '#0d0906'],
+ },
+ style: {
+ opacity: 1,
+ objectFit: 'cover',
+ overlay: {
+ enabled: true,
+ type: 'gradient',
+ direction: 'to-bottom',
+ stops: [
+ { position: 0, color: 'rgba(26,21,15,0)' },
+ { position: 0.4, color: 'rgba(26,21,15,0.3)' },
+ { position: 0.7, color: 'rgba(26,21,15,0.7)' },
+ { position: 1, color: 'rgba(26,21,15,0.95)' },
+ ],
+ },
+ },
+ },
+ {
+ id: 'layer_main_image',
+ type: 'mainImage',
+ zIndex: 1,
+ position: { x: 0.5, y: 0.25, w: 0.88, h: 0.32 },
+ anchor: 'center',
+ required: false,
+ style: {
+ borderRadius: 20,
+ objectFit: 'cover',
+ shadow: { enabled: true, blur: 35, offsetY: 15, color: 'rgba(0,0,0,0.6)' },
+ border: { enabled: true, width: 3, color: 'rgba(139,90,43,0.4)' },
+ },
+ },
+ {
+ id: 'layer_decorative_top',
+ type: 'shape',
+ zIndex: 2,
+ position: { x: 0.5, y: 0.02, w: 0.3, h: 0.003 },
+ anchor: 'top-center',
+ required: false,
+ style: { shape: 'rectangle', fill: '#8b5a2b', opacity: 0.6, borderRadius: 2 },
+ },
+ {
+ id: 'layer_title',
+ type: 'title',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.48, w: 0.92, h: 0.12 },
+ anchor: 'center',
+ required: true,
+ maxLines: 2,
+ style: {
+ fontFamily: 'Playfair Display',
+ fontWeight: 700,
+ fontSize: 62,
+ color: '#f4e9d9',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 3,
+ lineHeight: 1.1,
+ shadow: { enabled: true, blur: 12, offsetX: 2, offsetY: 3, color: 'rgba(0,0,0,0.9)' },
+ },
+ },
+ {
+ id: 'layer_subtitle',
+ type: 'subtitle',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.58, w: 0.85, h: 0.05 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 26,
+ fontStyle: 'italic',
+ color: '#c9b896',
+ textAlign: 'center',
+ letterSpacing: 1,
+ },
+ },
+ {
+ id: 'layer_divider',
+ type: 'divider',
+ zIndex: 9,
+ position: { x: 0.5, y: 0.635, w: 0.25, h: 0.002 },
+ anchor: 'center',
+ style: { color: '#8b5a2b', width: 2, opacity: 0.7 },
+ },
+ {
+ id: 'layer_event_date',
+ type: 'eventDate',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.69, w: 0.9, h: 0.06 },
+ anchor: 'center',
+ required: true,
+ style: {
+ fontFamily: 'Bebas Neue',
+ fontWeight: 400,
+ fontSize: 52,
+ color: '#d4a854',
+ textAlign: 'center',
+ letterSpacing: 4,
+ textTransform: 'uppercase',
+ shadow: { enabled: true, blur: 8, offsetY: 2, color: 'rgba(0,0,0,0.8)' },
+ },
+ },
+ {
+ id: 'layer_location',
+ type: 'location',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.77, w: 0.85, h: 0.045 },
+ anchor: 'center',
+ required: true,
+ icon: { enabled: true, name: 'forest', size: 24, color: '#6b8e23' },
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 600,
+ fontSize: 26,
+ color: '#e8dcc8',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_contacts',
+ type: 'contacts',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.84, w: 0.9, h: 0.04 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 20,
+ color: '#a89880',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_extra',
+ type: 'extraText',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.9, w: 0.85, h: 0.03 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 16,
+ fontStyle: 'italic',
+ color: '#8a7a68',
+ textAlign: 'center',
+ },
+ },
+ ],
+
+ logoSlots: {
+ enabled: true,
+ maxCount: 3,
+ collapseIfEmpty: true,
+ slots: [
+ {
+ id: 'logo_1',
+ position: { x: 0.15, y: 0.96, w: 0.12, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ {
+ id: 'logo_2',
+ position: { x: 0.5, y: 0.96, w: 0.12, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ {
+ id: 'logo_3',
+ position: { x: 0.85, y: 0.96, w: 0.12, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ ],
+ },
+
+ palette: {
+ primary: '#8b5a2b',
+ secondary: '#2d3a1f',
+ accent: '#d4a854',
+ background: '#1a150f',
+ text: '#f4e9d9',
+ textSecondary: '#c9b896',
+ textMuted: '#8a7a68',
+ },
+
+ typography: {
+ titleFont: 'Playfair Display',
+ headingFont: 'Bebas Neue',
+ bodyFont: 'Open Sans',
+ accentFont: 'Playfair Display',
+ },
+
+ defaultAiPromptHints: {
+ backgroundImage:
+ 'mystical autumn forest, morning mist, golden light filtering through oak trees, porcini mushrooms on forest floor, photorealistic, cinematic, no text',
+ mainImage:
+ 'wicker basket full of fresh porcini mushrooms, forest background, warm natural light, rustic style, no text',
+ },
+
+ metadata: {
+ author: 'System',
+ version: '1.0.0',
+ tags: ['funghi', 'natura', 'bosco', 'autunno', 'sagra', 'outdoor'],
+ isPublic: true,
+ },
+ },
+
+ // ============================================
+ // 2. AUTOCOSTRUZIONE FORNO IN TERRA CRUDA
+ // ============================================
+ {
+ name: 'Workshop Forno in Terra Cruda',
+ templateType: 'workshop-craft',
+ description: 'Template per laboratori di bioedilizia, autocostruzione, tecniche tradizionali con terra cruda',
+
+ format: {
+ preset: 'instagram-story',
+ width: 1080,
+ height: 1920,
+ unit: 'px',
+ dpi: 72,
+ },
+
+ safeArea: { top: 0.03, right: 0.04, bottom: 0.03, left: 0.04 },
+ backgroundColor: '#2a1f1a',
+
+ layers: [
+ {
+ id: 'layer_bg',
+ type: 'backgroundImage',
+ zIndex: 0,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ required: false,
+ fallback: {
+ type: 'gradient',
+ direction: 'to-bottom',
+ colors: ['#5d4037', '#3e2723', '#1a1210'],
+ },
+ style: {
+ opacity: 1,
+ objectFit: 'cover',
+ overlay: {
+ enabled: true,
+ type: 'gradient',
+ direction: 'to-bottom',
+ stops: [
+ { position: 0, color: 'rgba(42,31,26,0.1)' },
+ { position: 0.35, color: 'rgba(42,31,26,0.4)' },
+ { position: 0.65, color: 'rgba(42,31,26,0.75)' },
+ { position: 1, color: 'rgba(42,31,26,0.95)' },
+ ],
+ },
+ },
+ },
+ {
+ id: 'layer_main_image',
+ type: 'mainImage',
+ zIndex: 1,
+ position: { x: 0.5, y: 0.26, w: 0.85, h: 0.3 },
+ anchor: 'center',
+ required: false,
+ style: {
+ borderRadius: 16,
+ objectFit: 'cover',
+ shadow: { enabled: true, blur: 40, offsetY: 18, color: 'rgba(0,0,0,0.55)' },
+ border: { enabled: true, width: 4, color: 'rgba(188,143,92,0.35)' },
+ },
+ },
+ {
+ id: 'layer_badge',
+ type: 'shape',
+ zIndex: 5,
+ position: { x: 0.5, y: 0.43, w: 0.35, h: 0.035 },
+ anchor: 'center',
+ style: { shape: 'rectangle', fill: '#bc8f5c', opacity: 0.9, borderRadius: 20 },
+ },
+ {
+ id: 'layer_badge_text',
+ type: 'customText',
+ zIndex: 6,
+ position: { x: 0.5, y: 0.43, w: 0.33, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 700,
+ fontSize: 16,
+ color: '#1a1210',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 3,
+ },
+ },
+ {
+ id: 'layer_title',
+ type: 'title',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.52, w: 0.92, h: 0.13 },
+ anchor: 'center',
+ required: true,
+ maxLines: 3,
+ style: {
+ fontFamily: 'Montserrat',
+ fontWeight: 900,
+ fontSize: 52,
+ color: '#faf3e8',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 2,
+ lineHeight: 1.05,
+ shadow: { enabled: true, blur: 15, offsetY: 4, color: 'rgba(0,0,0,0.85)' },
+ },
+ },
+ {
+ id: 'layer_subtitle',
+ type: 'subtitle',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.635, w: 0.88, h: 0.045 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 24,
+ color: '#d4c4a8',
+ textAlign: 'center',
+ lineHeight: 1.4,
+ },
+ },
+ {
+ id: 'layer_event_date',
+ type: 'eventDate',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.72, w: 0.9, h: 0.055 },
+ anchor: 'center',
+ required: true,
+ style: {
+ fontFamily: 'Bebas Neue',
+ fontWeight: 400,
+ fontSize: 50,
+ color: '#e8a54b',
+ textAlign: 'center',
+ letterSpacing: 5,
+ textTransform: 'uppercase',
+ shadow: { enabled: true, blur: 10, offsetY: 2, color: 'rgba(0,0,0,0.7)' },
+ },
+ },
+ {
+ id: 'layer_location',
+ type: 'location',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.79, w: 0.85, h: 0.04 },
+ anchor: 'center',
+ required: true,
+ icon: { enabled: true, name: 'place', size: 22, color: '#bc8f5c' },
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 600,
+ fontSize: 24,
+ color: '#f0e6d8',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_contacts',
+ type: 'contacts',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.855, w: 0.9, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 19,
+ color: '#b8a88c',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_extra',
+ type: 'extraText',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.905, w: 0.85, h: 0.03 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 15,
+ color: '#9a8a70',
+ textAlign: 'center',
+ },
+ },
+ ],
+
+ logoSlots: {
+ enabled: true,
+ maxCount: 2,
+ collapseIfEmpty: true,
+ slots: [
+ {
+ id: 'logo_1',
+ position: { x: 0.25, y: 0.96, w: 0.14, h: 0.045 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.8 },
+ },
+ {
+ id: 'logo_2',
+ position: { x: 0.75, y: 0.96, w: 0.14, h: 0.045 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.8 },
+ },
+ ],
+ },
+
+ palette: {
+ primary: '#bc8f5c',
+ secondary: '#5d4037',
+ accent: '#e8a54b',
+ background: '#2a1f1a',
+ text: '#faf3e8',
+ textSecondary: '#d4c4a8',
+ textMuted: '#9a8a70',
+ },
+
+ typography: {
+ titleFont: 'Montserrat',
+ headingFont: 'Bebas Neue',
+ bodyFont: 'Open Sans',
+ accentFont: 'Montserrat',
+ },
+
+ defaultAiPromptHints: {
+ backgroundImage:
+ 'outdoor workshop scene, people building cob oven with bare hands, earth clay straw mixture, rustic countryside, warm afternoon light, documentary style, no text',
+ mainImage:
+ 'traditional earthen bread oven with fire inside, rustic garden setting, artisan craftsmanship, warm terracotta colors, no text',
+ },
+
+ metadata: {
+ author: 'System',
+ version: '1.0.0',
+ tags: ['workshop', 'terra cruda', 'bioedilizia', 'autocostruzione', 'forno', 'artigianato'],
+ isPublic: true,
+ },
+ },
+
+ // ============================================
+ // 3. RACCOLTA GRANO E MIETITURA
+ // ============================================
+ {
+ name: 'Festa della Mietitura',
+ templateType: 'harvest-tradition',
+ description: 'Template per eventi di raccolta grano, feste contadine, trebbiatura tradizionale',
+
+ format: {
+ preset: 'instagram-story',
+ width: 1080,
+ height: 1920,
+ unit: 'px',
+ dpi: 72,
+ },
+
+ safeArea: { top: 0.03, right: 0.04, bottom: 0.03, left: 0.04 },
+ backgroundColor: '#1f1a0f',
+
+ layers: [
+ {
+ id: 'layer_bg',
+ type: 'backgroundImage',
+ zIndex: 0,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ required: false,
+ fallback: {
+ type: 'gradient',
+ direction: 'to-bottom',
+ colors: ['#d4a03a', '#8b6914', '#1f1a0f'],
+ },
+ style: {
+ opacity: 1,
+ objectFit: 'cover',
+ overlay: {
+ enabled: true,
+ type: 'gradient',
+ direction: 'to-bottom',
+ stops: [
+ { position: 0, color: 'rgba(31,26,15,0)' },
+ { position: 0.3, color: 'rgba(31,26,15,0.25)' },
+ { position: 0.6, color: 'rgba(31,26,15,0.65)' },
+ { position: 1, color: 'rgba(31,26,15,0.92)' },
+ ],
+ },
+ },
+ },
+ {
+ id: 'layer_main_image',
+ type: 'mainImage',
+ zIndex: 1,
+ position: { x: 0.5, y: 0.24, w: 0.9, h: 0.3 },
+ anchor: 'center',
+ required: false,
+ style: {
+ borderRadius: 12,
+ objectFit: 'cover',
+ shadow: { enabled: true, blur: 30, offsetY: 12, color: 'rgba(0,0,0,0.5)' },
+ },
+ },
+ {
+ id: 'layer_wheat_icon',
+ type: 'customText',
+ zIndex: 8,
+ position: { x: 0.5, y: 0.42, w: 0.2, h: 0.04 },
+ anchor: 'center',
+ style: {
+ fontSize: 40,
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_title',
+ type: 'title',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.5, w: 0.92, h: 0.11 },
+ anchor: 'center',
+ required: true,
+ maxLines: 2,
+ style: {
+ fontFamily: 'Playfair Display',
+ fontWeight: 700,
+ fontSize: 58,
+ color: '#fff8e7',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 4,
+ lineHeight: 1.08,
+ shadow: { enabled: true, blur: 14, offsetY: 3, color: 'rgba(0,0,0,0.85)' },
+ },
+ },
+ {
+ id: 'layer_subtitle',
+ type: 'subtitle',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.595, w: 0.85, h: 0.045 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 24,
+ fontStyle: 'italic',
+ color: '#e8d9b8',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_event_date',
+ type: 'eventDate',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.69, w: 0.9, h: 0.058 },
+ anchor: 'center',
+ required: true,
+ style: {
+ fontFamily: 'Bebas Neue',
+ fontWeight: 400,
+ fontSize: 54,
+ color: '#f5c542',
+ textAlign: 'center',
+ letterSpacing: 6,
+ textTransform: 'uppercase',
+ shadow: { enabled: true, blur: 10, offsetY: 2, color: 'rgba(0,0,0,0.75)' },
+ },
+ },
+ {
+ id: 'layer_location',
+ type: 'location',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.77, w: 0.85, h: 0.042 },
+ anchor: 'center',
+ required: true,
+ icon: { enabled: true, name: 'grass', size: 24, color: '#c9a227' },
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 600,
+ fontSize: 25,
+ color: '#f8f0dc',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_contacts',
+ type: 'contacts',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.84, w: 0.9, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 19,
+ color: '#c4b694',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_extra',
+ type: 'extraText',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.895, w: 0.85, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 16,
+ color: '#a89868',
+ textAlign: 'center',
+ },
+ },
+ ],
+
+ logoSlots: {
+ enabled: true,
+ maxCount: 3,
+ collapseIfEmpty: true,
+ slots: [
+ {
+ id: 'logo_1',
+ position: { x: 0.15, y: 0.96, w: 0.11, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ {
+ id: 'logo_2',
+ position: { x: 0.5, y: 0.96, w: 0.11, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ {
+ id: 'logo_3',
+ position: { x: 0.85, y: 0.96, w: 0.11, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ ],
+ },
+
+ palette: {
+ primary: '#c9a227',
+ secondary: '#8b6914',
+ accent: '#f5c542',
+ background: '#1f1a0f',
+ text: '#fff8e7',
+ textSecondary: '#e8d9b8',
+ textMuted: '#a89868',
+ },
+
+ typography: {
+ titleFont: 'Playfair Display',
+ headingFont: 'Bebas Neue',
+ bodyFont: 'Open Sans',
+ accentFont: 'Playfair Display',
+ },
+
+ defaultAiPromptHints: {
+ backgroundImage:
+ 'golden wheat field at sunset, warm summer light, rolling hills, rural Italian landscape, harvest season, cinematic photography, no text',
+ mainImage:
+ 'farmers harvesting wheat with traditional scythes, vintage rural scene, golden hour, documentary style, no text',
+ },
+
+ metadata: {
+ author: 'System',
+ version: '1.0.0',
+ tags: ['grano', 'mietitura', 'raccolta', 'contadino', 'tradizione', 'estate'],
+ isPublic: true,
+ },
+ },
+
+ // ============================================
+ // 4. COSTRUZIONE CASE IN TERRA / BIOEDILIZIA
+ // ============================================
+ {
+ name: 'Workshop Case in Terra',
+ templateType: 'eco-building',
+ description: 'Template per workshop di bioedilizia, case in terra cruda, cob building, permacultura costruttiva',
+
+ format: {
+ preset: 'instagram-story',
+ width: 1080,
+ height: 1920,
+ unit: 'px',
+ dpi: 72,
+ },
+
+ safeArea: { top: 0.03, right: 0.04, bottom: 0.03, left: 0.04 },
+ backgroundColor: '#1a1612',
+
+ layers: [
+ {
+ id: 'layer_bg',
+ type: 'backgroundImage',
+ zIndex: 0,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ required: false,
+ fallback: {
+ type: 'gradient',
+ direction: 'to-bottom',
+ colors: ['#6d5a42', '#3d3228', '#1a1612'],
+ },
+ style: {
+ opacity: 1,
+ objectFit: 'cover',
+ overlay: {
+ enabled: true,
+ type: 'gradient',
+ direction: 'to-bottom',
+ stops: [
+ { position: 0, color: 'rgba(26,22,18,0.1)' },
+ { position: 0.4, color: 'rgba(26,22,18,0.5)' },
+ { position: 0.7, color: 'rgba(26,22,18,0.8)' },
+ { position: 1, color: 'rgba(26,22,18,0.96)' },
+ ],
+ },
+ },
+ },
+ {
+ id: 'layer_main_image',
+ type: 'mainImage',
+ zIndex: 1,
+ position: { x: 0.5, y: 0.25, w: 0.88, h: 0.32 },
+ anchor: 'center',
+ required: false,
+ style: {
+ borderRadius: 18,
+ objectFit: 'cover',
+ shadow: { enabled: true, blur: 35, offsetY: 15, color: 'rgba(0,0,0,0.55)' },
+ },
+ },
+ {
+ id: 'layer_eco_badge',
+ type: 'shape',
+ zIndex: 5,
+ position: { x: 0.82, y: 0.12, w: 0.14, h: 0.065 },
+ anchor: 'center',
+ style: { shape: 'circle', fill: '#4a7c4e', opacity: 0.92, borderRadius: 100 },
+ },
+ {
+ id: 'layer_title',
+ type: 'title',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.49, w: 0.92, h: 0.13 },
+ anchor: 'center',
+ required: true,
+ maxLines: 3,
+ style: {
+ fontFamily: 'Montserrat',
+ fontWeight: 800,
+ fontSize: 50,
+ color: '#f5ede0',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 2,
+ lineHeight: 1.08,
+ shadow: { enabled: true, blur: 12, offsetY: 3, color: 'rgba(0,0,0,0.85)' },
+ },
+ },
+ {
+ id: 'layer_subtitle',
+ type: 'subtitle',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.615, w: 0.88, h: 0.05 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 23,
+ color: '#c8baa0',
+ textAlign: 'center',
+ lineHeight: 1.4,
+ },
+ },
+ {
+ id: 'layer_event_date',
+ type: 'eventDate',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.71, w: 0.9, h: 0.055 },
+ anchor: 'center',
+ required: true,
+ style: {
+ fontFamily: 'Bebas Neue',
+ fontWeight: 400,
+ fontSize: 50,
+ color: '#7fb069',
+ textAlign: 'center',
+ letterSpacing: 5,
+ textTransform: 'uppercase',
+ shadow: { enabled: true, blur: 8, offsetY: 2, color: 'rgba(0,0,0,0.7)' },
+ },
+ },
+ {
+ id: 'layer_location',
+ type: 'location',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.785, w: 0.85, h: 0.04 },
+ anchor: 'center',
+ required: true,
+ icon: { enabled: true, name: 'eco', size: 22, color: '#7fb069' },
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 600,
+ fontSize: 24,
+ color: '#ebe3d4',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_contacts',
+ type: 'contacts',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.85, w: 0.9, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 18,
+ color: '#a89a80',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_extra',
+ type: 'extraText',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.9, w: 0.85, h: 0.03 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 15,
+ color: '#8a7c64',
+ textAlign: 'center',
+ },
+ },
+ ],
+
+ logoSlots: {
+ enabled: true,
+ maxCount: 2,
+ collapseIfEmpty: true,
+ slots: [
+ {
+ id: 'logo_1',
+ position: { x: 0.28, y: 0.96, w: 0.14, h: 0.042 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.82 },
+ },
+ {
+ id: 'logo_2',
+ position: { x: 0.72, y: 0.96, w: 0.14, h: 0.042 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.82 },
+ },
+ ],
+ },
+
+ palette: {
+ primary: '#7fb069',
+ secondary: '#6d5a42',
+ accent: '#4a7c4e',
+ background: '#1a1612',
+ text: '#f5ede0',
+ textSecondary: '#c8baa0',
+ textMuted: '#8a7c64',
+ },
+
+ typography: {
+ titleFont: 'Montserrat',
+ headingFont: 'Bebas Neue',
+ bodyFont: 'Open Sans',
+ accentFont: 'Montserrat',
+ },
+
+ defaultAiPromptHints: {
+ backgroundImage:
+ 'earthen house construction, cob building process, natural materials, rural eco-village, warm afternoon light, documentary style, no text',
+ mainImage:
+ 'people building with earth clay straw, hands working together, sustainable architecture, natural building workshop, no text',
+ },
+
+ metadata: {
+ author: 'System',
+ version: '1.0.0',
+ tags: ['bioedilizia', 'terra cruda', 'case', 'cob', 'permacultura', 'sostenibile', 'workshop'],
+ isPublic: true,
+ },
+ },
+
+ // ============================================
+ // 5. FESTA DEL SOLSTIZIO / EQUINOZIO
+ // ============================================
+ {
+ name: 'Celebrazione Solstizio',
+ templateType: 'spiritual-nature',
+ description:
+ 'Template per celebrazioni solstizio, equinozio, rituali stagionali, cerchi sacri, eventi spirituali nella natura',
+
+ format: {
+ preset: 'instagram-story',
+ width: 1080,
+ height: 1920,
+ unit: 'px',
+ dpi: 72,
+ },
+
+ safeArea: { top: 0.03, right: 0.04, bottom: 0.03, left: 0.04 },
+ backgroundColor: '#0d0a14',
+
+ layers: [
+ {
+ id: 'layer_bg',
+ type: 'backgroundImage',
+ zIndex: 0,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ required: false,
+ fallback: {
+ type: 'gradient',
+ direction: 'to-bottom',
+ colors: ['#1a1040', '#2d1b4e', '#0d0a14'],
+ },
+ style: {
+ opacity: 1,
+ objectFit: 'cover',
+ overlay: {
+ enabled: true,
+ type: 'gradient',
+ direction: 'to-bottom',
+ stops: [
+ { position: 0, color: 'rgba(13,10,20,0)' },
+ { position: 0.35, color: 'rgba(13,10,20,0.35)' },
+ { position: 0.65, color: 'rgba(13,10,20,0.7)' },
+ { position: 1, color: 'rgba(13,10,20,0.94)' },
+ ],
+ },
+ },
+ },
+ {
+ id: 'layer_main_image',
+ type: 'mainImage',
+ zIndex: 1,
+ position: { x: 0.5, y: 0.27, w: 0.72, h: 0.28 },
+ anchor: 'center',
+ required: false,
+ style: {
+ borderRadius: 200,
+ objectFit: 'cover',
+ shadow: { enabled: true, blur: 50, offsetY: 0, color: 'rgba(147,112,219,0.4)' },
+ border: { enabled: true, width: 3, color: 'rgba(212,175,55,0.5)' },
+ },
+ },
+ {
+ id: 'layer_title',
+ type: 'title',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.5, w: 0.92, h: 0.11 },
+ anchor: 'center',
+ required: true,
+ maxLines: 2,
+ style: {
+ fontFamily: 'Playfair Display',
+ fontWeight: 700,
+ fontSize: 56,
+ color: '#f0e6ff',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 6,
+ lineHeight: 1.1,
+ shadow: { enabled: true, blur: 20, offsetY: 0, color: 'rgba(147,112,219,0.6)' },
+ },
+ },
+ {
+ id: 'layer_subtitle',
+ type: 'subtitle',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.6, w: 0.85, h: 0.05 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 24,
+ fontStyle: 'italic',
+ color: '#c4b8e0',
+ textAlign: 'center',
+ letterSpacing: 2,
+ },
+ },
+ {
+ id: 'layer_divider_moon',
+ type: 'customText',
+ zIndex: 8,
+ position: { x: 0.5, y: 0.665, w: 0.3, h: 0.03 },
+ anchor: 'center',
+ style: { fontSize: 24, textAlign: 'center', color: '#d4af37' },
+ },
+ {
+ id: 'layer_event_date',
+ type: 'eventDate',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.72, w: 0.9, h: 0.055 },
+ anchor: 'center',
+ required: true,
+ style: {
+ fontFamily: 'Bebas Neue',
+ fontWeight: 400,
+ fontSize: 48,
+ color: '#d4af37',
+ textAlign: 'center',
+ letterSpacing: 5,
+ textTransform: 'uppercase',
+ shadow: { enabled: true, blur: 12, offsetY: 0, color: 'rgba(212,175,55,0.5)' },
+ },
+ },
+ {
+ id: 'layer_location',
+ type: 'location',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.79, w: 0.85, h: 0.04 },
+ anchor: 'center',
+ required: true,
+ icon: { enabled: true, name: 'nights_stay', size: 22, color: '#9370db' },
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 500,
+ fontSize: 24,
+ color: '#e8e0f4',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_contacts',
+ type: 'contacts',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.855, w: 0.9, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 18,
+ color: '#a090c0',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_extra',
+ type: 'extraText',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.905, w: 0.85, h: 0.03 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 15,
+ fontStyle: 'italic',
+ color: '#8070a0',
+ textAlign: 'center',
+ },
+ },
+ ],
+
+ logoSlots: {
+ enabled: true,
+ maxCount: 2,
+ collapseIfEmpty: true,
+ slots: [
+ {
+ id: 'logo_1',
+ position: { x: 0.25, y: 0.96, w: 0.12, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.75 },
+ },
+ {
+ id: 'logo_2',
+ position: { x: 0.75, y: 0.96, w: 0.12, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.75 },
+ },
+ ],
+ },
+
+ palette: {
+ primary: '#9370db',
+ secondary: '#2d1b4e',
+ accent: '#d4af37',
+ background: '#0d0a14',
+ text: '#f0e6ff',
+ textSecondary: '#c4b8e0',
+ textMuted: '#8070a0',
+ },
+
+ typography: {
+ titleFont: 'Playfair Display',
+ headingFont: 'Bebas Neue',
+ bodyFont: 'Open Sans',
+ accentFont: 'Playfair Display',
+ },
+
+ defaultAiPromptHints: {
+ backgroundImage:
+ 'mystical night sky with stars and aurora, ancient stone circle, solstice moonlight, ethereal purple blue atmosphere, fantasy art style, no text',
+ mainImage: 'full moon rising over sacred landscape, spiritual ceremony at night, mystical atmosphere, no text',
+ },
+
+ metadata: {
+ author: 'System',
+ version: '1.0.0',
+ tags: ['solstizio', 'equinozio', 'spirituale', 'natura', 'luna', 'rituale', 'cerchio'],
+ isPublic: true,
+ },
+ },
+
+ // ============================================
+ // 6. MERCATO CONTADINO / FARMERS MARKET
+ // ============================================
+ {
+ name: 'Mercato Contadino Bio',
+ templateType: 'farmers-market',
+ description: 'Template per mercati contadini, vendita diretta, prodotti biologici, km zero',
+
+ format: {
+ preset: 'instagram-story',
+ width: 1080,
+ height: 1920,
+ unit: 'px',
+ dpi: 72,
+ },
+
+ safeArea: { top: 0.03, right: 0.04, bottom: 0.03, left: 0.04 },
+ backgroundColor: '#f5f0e6',
+
+ layers: [
+ {
+ id: 'layer_bg',
+ type: 'backgroundImage',
+ zIndex: 0,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ required: false,
+ fallback: {
+ type: 'gradient',
+ direction: 'to-bottom',
+ colors: ['#f5f0e6', '#e8dcc8', '#d4c4a8'],
+ },
+ style: {
+ opacity: 1,
+ objectFit: 'cover',
+ overlay: {
+ enabled: true,
+ type: 'gradient',
+ direction: 'to-bottom',
+ stops: [
+ { position: 0, color: 'rgba(245,240,230,0.3)' },
+ { position: 0.5, color: 'rgba(245,240,230,0.6)' },
+ { position: 1, color: 'rgba(245,240,230,0.92)' },
+ ],
+ },
+ },
+ },
+ {
+ id: 'layer_header_band',
+ type: 'shape',
+ zIndex: 2,
+ position: { x: 0, y: 0, w: 1, h: 0.08 },
+ anchor: 'top-left',
+ style: { shape: 'rectangle', fill: '#2e7d32', opacity: 1 },
+ },
+ {
+ id: 'layer_header_text',
+ type: 'customText',
+ zIndex: 3,
+ position: { x: 0.5, y: 0.04, w: 0.9, h: 0.04 },
+ anchor: 'center',
+ style: {
+ fontFamily: 'Montserrat',
+ fontWeight: 700,
+ fontSize: 20,
+ color: '#ffffff',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 8,
+ },
+ },
+ {
+ id: 'layer_main_image',
+ type: 'mainImage',
+ zIndex: 1,
+ position: { x: 0.5, y: 0.3, w: 0.88, h: 0.3 },
+ anchor: 'center',
+ required: false,
+ style: {
+ borderRadius: 16,
+ objectFit: 'cover',
+ shadow: { enabled: true, blur: 25, offsetY: 10, color: 'rgba(0,0,0,0.2)' },
+ border: { enabled: true, width: 4, color: '#ffffff' },
+ },
+ },
+ {
+ id: 'layer_title',
+ type: 'title',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.52, w: 0.92, h: 0.1 },
+ anchor: 'center',
+ required: true,
+ maxLines: 2,
+ style: {
+ fontFamily: 'Playfair Display',
+ fontWeight: 700,
+ fontSize: 54,
+ color: '#2e5a1c',
+ textAlign: 'center',
+ letterSpacing: 2,
+ lineHeight: 1.1,
+ shadow: { enabled: false },
+ },
+ },
+ {
+ id: 'layer_subtitle',
+ type: 'subtitle',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.61, w: 0.85, h: 0.045 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 22,
+ color: '#5a7a3a',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_divider',
+ type: 'divider',
+ zIndex: 9,
+ position: { x: 0.5, y: 0.67, w: 0.35, h: 0.002 },
+ anchor: 'center',
+ style: { color: '#2e7d32', width: 3, opacity: 0.6 },
+ },
+ {
+ id: 'layer_event_date',
+ type: 'eventDate',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.72, w: 0.9, h: 0.055 },
+ anchor: 'center',
+ required: true,
+ style: {
+ fontFamily: 'Bebas Neue',
+ fontWeight: 400,
+ fontSize: 50,
+ color: '#d35400',
+ textAlign: 'center',
+ letterSpacing: 4,
+ textTransform: 'uppercase',
+ },
+ },
+ {
+ id: 'layer_location',
+ type: 'location',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.79, w: 0.85, h: 0.04 },
+ anchor: 'center',
+ required: true,
+ icon: { enabled: true, name: 'storefront', size: 22, color: '#2e7d32' },
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 600,
+ fontSize: 24,
+ color: '#3a5a28',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_contacts',
+ type: 'contacts',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.855, w: 0.9, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 18,
+ color: '#6a8a5a',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_extra',
+ type: 'extraText',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.905, w: 0.85, h: 0.03 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 600,
+ fontSize: 16,
+ color: '#2e7d32',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 2,
+ },
+ },
+ ],
+
+ logoSlots: {
+ enabled: true,
+ maxCount: 3,
+ collapseIfEmpty: true,
+ slots: [
+ {
+ id: 'logo_1',
+ position: { x: 0.15, y: 0.96, w: 0.12, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.9 },
+ },
+ {
+ id: 'logo_2',
+ position: { x: 0.5, y: 0.96, w: 0.12, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.9 },
+ },
+ {
+ id: 'logo_3',
+ position: { x: 0.85, y: 0.96, w: 0.12, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.9 },
+ },
+ ],
+ },
+
+ palette: {
+ primary: '#2e7d32',
+ secondary: '#5a7a3a',
+ accent: '#d35400',
+ background: '#f5f0e6',
+ text: '#2e5a1c',
+ textSecondary: '#5a7a3a',
+ textMuted: '#6a8a5a',
+ },
+
+ typography: {
+ titleFont: 'Playfair Display',
+ headingFont: 'Bebas Neue',
+ bodyFont: 'Open Sans',
+ accentFont: 'Montserrat',
+ },
+
+ defaultAiPromptHints: {
+ backgroundImage:
+ "fresh organic vegetables on rustic wooden table, farmer's market display, natural daylight, soft focus background, warm colors, no text",
+ mainImage:
+ 'colorful seasonal produce, tomatoes zucchini peppers herbs in wooden crates, authentic farmers market, no text',
+ },
+
+ metadata: {
+ author: 'System',
+ version: '1.0.0',
+ tags: ['mercato', 'contadino', 'bio', 'km zero', 'prodotti', 'agricoltura'],
+ isPublic: true,
+ },
+ },
+
+ // ============================================
+ // 7. CORSO DI APICOLTURA NATURALE
+ // ============================================
+ {
+ name: 'Corso Apicoltura Naturale',
+ templateType: 'beekeeping-course',
+ description: 'Template per corsi di apicoltura, laboratori sulle api, miele, biodiversità',
+
+ format: {
+ preset: 'instagram-story',
+ width: 1080,
+ height: 1920,
+ unit: 'px',
+ dpi: 72,
+ },
+
+ safeArea: { top: 0.03, right: 0.04, bottom: 0.03, left: 0.04 },
+ backgroundColor: '#1a1408',
+
+ layers: [
+ {
+ id: 'layer_bg',
+ type: 'backgroundImage',
+ zIndex: 0,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ required: false,
+ fallback: {
+ type: 'gradient',
+ direction: 'to-bottom',
+ colors: ['#6b5a2e', '#3a3018', '#1a1408'],
+ },
+ style: {
+ opacity: 1,
+ objectFit: 'cover',
+ overlay: {
+ enabled: true,
+ type: 'gradient',
+ direction: 'to-bottom',
+ stops: [
+ { position: 0, color: 'rgba(26,20,8,0.15)' },
+ { position: 0.4, color: 'rgba(26,20,8,0.5)' },
+ { position: 0.7, color: 'rgba(26,20,8,0.8)' },
+ { position: 1, color: 'rgba(26,20,8,0.96)' },
+ ],
+ },
+ },
+ },
+ {
+ id: 'layer_honeycomb_pattern',
+ type: 'shape',
+ zIndex: 1,
+ position: { x: 0, y: 0.85, w: 1, h: 0.15 },
+ anchor: 'top-left',
+ style: { shape: 'rectangle', fill: 'rgba(218,165,32,0.08)', opacity: 1 },
+ },
+ {
+ id: 'layer_main_image',
+ type: 'mainImage',
+ zIndex: 2,
+ position: { x: 0.5, y: 0.25, w: 0.75, h: 0.28 },
+ anchor: 'center',
+ required: false,
+ style: {
+ borderRadius: 140,
+ objectFit: 'cover',
+ shadow: { enabled: true, blur: 40, offsetY: 15, color: 'rgba(218,165,32,0.3)' },
+ border: { enabled: true, width: 5, color: 'rgba(218,165,32,0.6)' },
+ },
+ },
+ {
+ id: 'layer_bee_icon',
+ type: 'customText',
+ zIndex: 5,
+ position: { x: 0.5, y: 0.42, w: 0.15, h: 0.04 },
+ anchor: 'center',
+ style: { fontSize: 36, textAlign: 'center' },
+ },
+ {
+ id: 'layer_title',
+ type: 'title',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.51, w: 0.92, h: 0.11 },
+ anchor: 'center',
+ required: true,
+ maxLines: 2,
+ style: {
+ fontFamily: 'Montserrat',
+ fontWeight: 800,
+ fontSize: 50,
+ color: '#fff8dc',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 3,
+ lineHeight: 1.1,
+ shadow: { enabled: true, blur: 15, offsetY: 4, color: 'rgba(0,0,0,0.8)' },
+ },
+ },
+ {
+ id: 'layer_subtitle',
+ type: 'subtitle',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.615, w: 0.85, h: 0.05 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 23,
+ color: '#e8d4a8',
+ textAlign: 'center',
+ lineHeight: 1.4,
+ },
+ },
+ {
+ id: 'layer_event_date',
+ type: 'eventDate',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.71, w: 0.9, h: 0.055 },
+ anchor: 'center',
+ required: true,
+ style: {
+ fontFamily: 'Bebas Neue',
+ fontWeight: 400,
+ fontSize: 50,
+ color: '#daa520',
+ textAlign: 'center',
+ letterSpacing: 5,
+ textTransform: 'uppercase',
+ shadow: { enabled: true, blur: 10, offsetY: 2, color: 'rgba(0,0,0,0.7)' },
+ },
+ },
+ {
+ id: 'layer_location',
+ type: 'location',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.785, w: 0.85, h: 0.04 },
+ anchor: 'center',
+ required: true,
+ icon: { enabled: true, name: 'hive', size: 22, color: '#daa520' },
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 600,
+ fontSize: 24,
+ color: '#f5ecd0',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_contacts',
+ type: 'contacts',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.85, w: 0.9, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 18,
+ color: '#c4a870',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_extra',
+ type: 'extraText',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.9, w: 0.85, h: 0.03 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 15,
+ color: '#a08850',
+ textAlign: 'center',
+ },
+ },
+ ],
+
+ logoSlots: {
+ enabled: true,
+ maxCount: 2,
+ collapseIfEmpty: true,
+ slots: [
+ {
+ id: 'logo_1',
+ position: { x: 0.28, y: 0.96, w: 0.13, h: 0.042 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ {
+ id: 'logo_2',
+ position: { x: 0.72, y: 0.96, w: 0.13, h: 0.042 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ ],
+ },
+
+ palette: {
+ primary: '#daa520',
+ secondary: '#6b5a2e',
+ accent: '#ffd700',
+ background: '#1a1408',
+ text: '#fff8dc',
+ textSecondary: '#e8d4a8',
+ textMuted: '#a08850',
+ },
+
+ typography: {
+ titleFont: 'Montserrat',
+ headingFont: 'Bebas Neue',
+ bodyFont: 'Open Sans',
+ accentFont: 'Montserrat',
+ },
+
+ defaultAiPromptHints: {
+ backgroundImage:
+ 'beehives in flower meadow, honeybees flying, golden afternoon light, lavender field background, dreamy atmosphere, no text',
+ mainImage: 'close-up of bees on honeycomb, golden honey dripping, macro photography, warm lighting, no text',
+ },
+
+ metadata: {
+ author: 'System',
+ version: '1.0.0',
+ tags: ['apicoltura', 'api', 'miele', 'corso', 'natura', 'biodiversità'],
+ isPublic: true,
+ },
+ },
+
+ // ============================================
+ // 8. FESTA DEL VINO NATURALE
+ // ============================================
+ {
+ name: 'Festa del Vino Naturale',
+ templateType: 'wine-festival',
+ description: 'Template per feste del vino, degustazioni, vendemmia, cantine aperte',
+
+ format: {
+ preset: 'instagram-story',
+ width: 1080,
+ height: 1920,
+ unit: 'px',
+ dpi: 72,
+ },
+
+ safeArea: { top: 0.03, right: 0.04, bottom: 0.03, left: 0.04 },
+ backgroundColor: '#1a0a15',
+
+ layers: [
+ {
+ id: 'layer_bg',
+ type: 'backgroundImage',
+ zIndex: 0,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ required: false,
+ fallback: {
+ type: 'gradient',
+ direction: 'to-bottom',
+ colors: ['#4a1942', '#2d1028', '#1a0a15'],
+ },
+ style: {
+ opacity: 1,
+ objectFit: 'cover',
+ overlay: {
+ enabled: true,
+ type: 'gradient',
+ direction: 'to-bottom',
+ stops: [
+ { position: 0, color: 'rgba(26,10,21,0.1)' },
+ { position: 0.35, color: 'rgba(26,10,21,0.45)' },
+ { position: 0.65, color: 'rgba(26,10,21,0.75)' },
+ { position: 1, color: 'rgba(26,10,21,0.95)' },
+ ],
+ },
+ },
+ },
+ {
+ id: 'layer_main_image',
+ type: 'mainImage',
+ zIndex: 1,
+ position: { x: 0.5, y: 0.26, w: 0.85, h: 0.3 },
+ anchor: 'center',
+ required: false,
+ style: {
+ borderRadius: 20,
+ objectFit: 'cover',
+ shadow: { enabled: true, blur: 40, offsetY: 15, color: 'rgba(128,0,64,0.4)' },
+ border: { enabled: true, width: 3, color: 'rgba(212,175,55,0.4)' },
+ },
+ },
+ {
+ id: 'layer_wine_icon',
+ type: 'customText',
+ zIndex: 5,
+ position: { x: 0.5, y: 0.44, w: 0.15, h: 0.04 },
+ anchor: 'center',
+ style: { fontSize: 32, textAlign: 'center' },
+ },
+ {
+ id: 'layer_title',
+ type: 'title',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.52, w: 0.92, h: 0.11 },
+ anchor: 'center',
+ required: true,
+ maxLines: 2,
+ style: {
+ fontFamily: 'Playfair Display',
+ fontWeight: 700,
+ fontSize: 56,
+ color: '#f8e8f0',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 4,
+ lineHeight: 1.08,
+ shadow: { enabled: true, blur: 15, offsetY: 3, color: 'rgba(0,0,0,0.85)' },
+ },
+ },
+ {
+ id: 'layer_subtitle',
+ type: 'subtitle',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.62, w: 0.85, h: 0.045 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 24,
+ fontStyle: 'italic',
+ color: '#d8b8c8',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_divider',
+ type: 'divider',
+ zIndex: 9,
+ position: { x: 0.5, y: 0.68, w: 0.25, h: 0.002 },
+ anchor: 'center',
+ style: { color: '#d4af37', width: 2, opacity: 0.7 },
+ },
+ {
+ id: 'layer_event_date',
+ type: 'eventDate',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.73, w: 0.9, h: 0.055 },
+ anchor: 'center',
+ required: true,
+ style: {
+ fontFamily: 'Bebas Neue',
+ fontWeight: 400,
+ fontSize: 50,
+ color: '#d4af37',
+ textAlign: 'center',
+ letterSpacing: 5,
+ textTransform: 'uppercase',
+ shadow: { enabled: true, blur: 10, offsetY: 2, color: 'rgba(0,0,0,0.7)' },
+ },
+ },
+ {
+ id: 'layer_location',
+ type: 'location',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.8, w: 0.85, h: 0.04 },
+ anchor: 'center',
+ required: true,
+ icon: { enabled: true, name: 'wine_bar', size: 22, color: '#8b2252' },
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 600,
+ fontSize: 24,
+ color: '#f0e0e8',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_contacts',
+ type: 'contacts',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.86, w: 0.9, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 18,
+ color: '#b090a0',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_extra',
+ type: 'extraText',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.91, w: 0.85, h: 0.03 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 15,
+ color: '#906878',
+ textAlign: 'center',
+ },
+ },
+ ],
+
+ logoSlots: {
+ enabled: true,
+ maxCount: 3,
+ collapseIfEmpty: true,
+ slots: [
+ {
+ id: 'logo_1',
+ position: { x: 0.15, y: 0.96, w: 0.11, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ {
+ id: 'logo_2',
+ position: { x: 0.5, y: 0.96, w: 0.11, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ {
+ id: 'logo_3',
+ position: { x: 0.85, y: 0.96, w: 0.11, h: 0.04 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ ],
+ },
+
+ palette: {
+ primary: '#8b2252',
+ secondary: '#4a1942',
+ accent: '#d4af37',
+ background: '#1a0a15',
+ text: '#f8e8f0',
+ textSecondary: '#d8b8c8',
+ textMuted: '#906878',
+ },
+
+ typography: {
+ titleFont: 'Playfair Display',
+ headingFont: 'Bebas Neue',
+ bodyFont: 'Open Sans',
+ accentFont: 'Playfair Display',
+ },
+
+ defaultAiPromptHints: {
+ backgroundImage:
+ 'vineyard at sunset, rows of grapevines, Tuscan hills, warm golden purple light, romantic atmosphere, no text',
+ mainImage:
+ 'wine glasses with red wine, rustic wooden barrel, grape bunches, warm candlelight, elegant still life, no text',
+ },
+
+ metadata: {
+ author: 'System',
+ version: '1.0.0',
+ tags: ['vino', 'naturale', 'degustazione', 'cantina', 'vendemmia', 'festa'],
+ isPublic: true,
+ },
+ },
+
+ // ============================================
+ // 9. YOGA E MEDITAZIONE NELLA NATURA (continuazione)
+ // ============================================
+ {
+ name: 'Ritiro Yoga nella Natura',
+ templateType: 'yoga-retreat',
+ description: 'Template per ritiri yoga, meditazione, benessere, mindfulness nella natura',
+
+ format: {
+ preset: 'instagram-story',
+ width: 1080,
+ height: 1920,
+ unit: 'px',
+ dpi: 72,
+ },
+
+ safeArea: { top: 0.03, right: 0.04, bottom: 0.03, left: 0.04 },
+ backgroundColor: '#0a1a18',
+
+ layers: [
+ {
+ id: 'layer_bg',
+ type: 'backgroundImage',
+ zIndex: 0,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ required: false,
+ fallback: {
+ type: 'gradient',
+ direction: 'to-bottom',
+ colors: ['#1a4a42', '#0f2a28', '#0a1a18'],
+ },
+ style: {
+ opacity: 1,
+ objectFit: 'cover',
+ overlay: {
+ enabled: true,
+ type: 'gradient',
+ direction: 'to-bottom',
+ stops: [
+ { position: 0, color: 'rgba(10,26,24,0.1)' },
+ { position: 0.4, color: 'rgba(10,26,24,0.4)' },
+ { position: 0.7, color: 'rgba(10,26,24,0.75)' },
+ { position: 1, color: 'rgba(10,26,24,0.95)' },
+ ],
+ },
+ },
+ },
+ {
+ id: 'layer_main_image',
+ type: 'mainImage',
+ zIndex: 1,
+ position: { x: 0.5, y: 0.27, w: 0.7, h: 0.26 },
+ anchor: 'center',
+ required: false,
+ style: {
+ borderRadius: 200,
+ objectFit: 'cover',
+ shadow: { enabled: true, blur: 45, offsetY: 0, color: 'rgba(77,182,172,0.3)' },
+ border: { enabled: true, width: 3, color: 'rgba(77,182,172,0.5)' },
+ },
+ },
+ {
+ id: 'layer_lotus',
+ type: 'customText',
+ zIndex: 5,
+ position: { x: 0.5, y: 0.43, w: 0.15, h: 0.04 },
+ anchor: 'center',
+ style: { fontSize: 32, textAlign: 'center' },
+ },
+ {
+ id: 'layer_title',
+ type: 'title',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.51, w: 0.92, h: 0.1 },
+ anchor: 'center',
+ required: true,
+ maxLines: 2,
+ style: {
+ fontFamily: 'Playfair Display',
+ fontWeight: 400,
+ fontSize: 52,
+ color: '#e8f8f4',
+ textAlign: 'center',
+ letterSpacing: 5,
+ lineHeight: 1.15,
+ shadow: { enabled: true, blur: 15, offsetY: 0, color: 'rgba(77,182,172,0.4)' },
+ },
+ },
+ {
+ id: 'layer_subtitle',
+ type: 'subtitle',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.605, w: 0.85, h: 0.05 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 23,
+ fontStyle: 'italic',
+ color: '#a8d8d0',
+ textAlign: 'center',
+ letterSpacing: 1,
+ },
+ },
+ {
+ id: 'layer_event_date',
+ type: 'eventDate',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.7, w: 0.9, h: 0.052 },
+ anchor: 'center',
+ required: true,
+ style: {
+ fontFamily: 'Bebas Neue',
+ fontWeight: 400,
+ fontSize: 46,
+ color: '#4db6ac',
+ textAlign: 'center',
+ letterSpacing: 5,
+ textTransform: 'uppercase',
+ shadow: { enabled: true, blur: 8, offsetY: 0, color: 'rgba(77,182,172,0.5)' },
+ },
+ },
+ {
+ id: 'layer_location',
+ type: 'location',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.77, w: 0.85, h: 0.04 },
+ anchor: 'center',
+ required: true,
+ icon: { enabled: true, name: 'self_improvement', size: 22, color: '#4db6ac' },
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 500,
+ fontSize: 24,
+ color: '#d0f0e8',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_contacts',
+ type: 'contacts',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.84, w: 0.9, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 18,
+ color: '#80b0a8',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_extra',
+ type: 'extraText',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.895, w: 0.85, h: 0.04 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 15,
+ color: '#608880',
+ textAlign: 'center',
+ lineHeight: 1.4,
+ },
+ },
+ ],
+
+ logoSlots: {
+ enabled: true,
+ maxCount: 2,
+ collapseIfEmpty: true,
+ slots: [
+ {
+ id: 'logo_1',
+ position: { x: 0.28, y: 0.96, w: 0.13, h: 0.042 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.8 },
+ },
+ {
+ id: 'logo_2',
+ position: { x: 0.72, y: 0.96, w: 0.13, h: 0.042 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.8 },
+ },
+ ],
+ },
+
+ palette: {
+ primary: '#4db6ac',
+ secondary: '#1a4a42',
+ accent: '#80cbc4',
+ background: '#0a1a18',
+ text: '#e8f8f4',
+ textSecondary: '#a8d8d0',
+ textMuted: '#608880',
+ },
+
+ typography: {
+ titleFont: 'Playfair Display',
+ headingFont: 'Bebas Neue',
+ bodyFont: 'Open Sans',
+ accentFont: 'Playfair Display',
+ },
+
+ defaultAiPromptHints: {
+ backgroundImage:
+ 'serene forest clearing at sunrise, morning mist, soft golden light through trees, peaceful meditation spot, zen atmosphere, no text',
+ mainImage:
+ 'person in yoga pose silhouette, mountain lake reflection, sunrise colors, peaceful spiritual moment, no text',
+ },
+
+ metadata: {
+ author: 'System',
+ version: '1.0.0',
+ tags: ['yoga', 'meditazione', 'ritiro', 'benessere', 'natura', 'mindfulness'],
+ isPublic: true,
+ },
+ },
+
+ // ============================================
+ // 10. LABORATORIO CERAMICA E ARGILLA
+ // ============================================
+ {
+ name: 'Laboratorio di Ceramica',
+ templateType: 'pottery-workshop',
+ description: 'Template per corsi di ceramica, lavorazione argilla, tornio, raku, artigianato artistico',
+
+ format: {
+ preset: 'instagram-story',
+ width: 1080,
+ height: 1920,
+ unit: 'px',
+ dpi: 72,
+ },
+
+ safeArea: { top: 0.03, right: 0.04, bottom: 0.03, left: 0.04 },
+ backgroundColor: '#1a1410',
+
+ layers: [
+ {
+ id: 'layer_bg',
+ type: 'backgroundImage',
+ zIndex: 0,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ required: false,
+ fallback: {
+ type: 'gradient',
+ direction: 'to-bottom',
+ colors: ['#5d4e42', '#3a2e26', '#1a1410'],
+ },
+ style: {
+ opacity: 1,
+ objectFit: 'cover',
+ overlay: {
+ enabled: true,
+ type: 'gradient',
+ direction: 'to-bottom',
+ stops: [
+ { position: 0, color: 'rgba(26,20,16,0.15)' },
+ { position: 0.4, color: 'rgba(26,20,16,0.5)' },
+ { position: 0.7, color: 'rgba(26,20,16,0.8)' },
+ { position: 1, color: 'rgba(26,20,16,0.96)' },
+ ],
+ },
+ },
+ },
+ {
+ id: 'layer_texture_overlay',
+ type: 'shape',
+ zIndex: 1,
+ position: { x: 0, y: 0, w: 1, h: 1 },
+ anchor: 'top-left',
+ style: { shape: 'rectangle', fill: 'rgba(139,90,43,0.05)', opacity: 1 },
+ },
+ {
+ id: 'layer_main_image',
+ type: 'mainImage',
+ zIndex: 2,
+ position: { x: 0.5, y: 0.26, w: 0.82, h: 0.3 },
+ anchor: 'center',
+ required: false,
+ style: {
+ borderRadius: 16,
+ objectFit: 'cover',
+ shadow: { enabled: true, blur: 35, offsetY: 15, color: 'rgba(0,0,0,0.5)' },
+ border: { enabled: true, width: 4, color: 'rgba(205,133,63,0.4)' },
+ },
+ },
+ {
+ id: 'layer_hands_badge',
+ type: 'shape',
+ zIndex: 4,
+ position: { x: 0.5, y: 0.435, w: 0.18, h: 0.042 },
+ anchor: 'center',
+ style: { shape: 'rectangle', fill: '#cd853f', opacity: 0.9, borderRadius: 25 },
+ },
+ {
+ id: 'layer_title',
+ type: 'title',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.52, w: 0.92, h: 0.11 },
+ anchor: 'center',
+ required: true,
+ maxLines: 2,
+ style: {
+ fontFamily: 'Playfair Display',
+ fontWeight: 700,
+ fontSize: 54,
+ color: '#faf0e6',
+ textAlign: 'center',
+ textTransform: 'uppercase',
+ letterSpacing: 3,
+ lineHeight: 1.1,
+ shadow: { enabled: true, blur: 12, offsetY: 3, color: 'rgba(0,0,0,0.85)' },
+ },
+ },
+ {
+ id: 'layer_subtitle',
+ type: 'subtitle',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.62, w: 0.85, h: 0.05 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 23,
+ fontStyle: 'italic',
+ color: '#dcc8b0',
+ textAlign: 'center',
+ lineHeight: 1.35,
+ },
+ },
+ {
+ id: 'layer_divider',
+ type: 'divider',
+ zIndex: 9,
+ position: { x: 0.5, y: 0.68, w: 0.22, h: 0.002 },
+ anchor: 'center',
+ style: { color: '#cd853f', width: 3, opacity: 0.7 },
+ },
+ {
+ id: 'layer_event_date',
+ type: 'eventDate',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.73, w: 0.9, h: 0.055 },
+ anchor: 'center',
+ required: true,
+ style: {
+ fontFamily: 'Bebas Neue',
+ fontWeight: 400,
+ fontSize: 50,
+ color: '#daa06d',
+ textAlign: 'center',
+ letterSpacing: 5,
+ textTransform: 'uppercase',
+ shadow: { enabled: true, blur: 10, offsetY: 2, color: 'rgba(0,0,0,0.7)' },
+ },
+ },
+ {
+ id: 'layer_location',
+ type: 'location',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.8, w: 0.85, h: 0.04 },
+ anchor: 'center',
+ required: true,
+ icon: { enabled: true, name: 'palette', size: 22, color: '#cd853f' },
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 600,
+ fontSize: 24,
+ color: '#f0e0d0',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_contacts',
+ type: 'contacts',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.86, w: 0.9, h: 0.035 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 400,
+ fontSize: 18,
+ color: '#b8a090',
+ textAlign: 'center',
+ },
+ },
+ {
+ id: 'layer_extra',
+ type: 'extraText',
+ zIndex: 10,
+ position: { x: 0.5, y: 0.91, w: 0.85, h: 0.03 },
+ anchor: 'center',
+ required: false,
+ style: {
+ fontFamily: 'Open Sans',
+ fontWeight: 300,
+ fontSize: 15,
+ color: '#8a7868',
+ textAlign: 'center',
+ },
+ },
+ ],
+
+ logoSlots: {
+ enabled: true,
+ maxCount: 2,
+ collapseIfEmpty: true,
+ slots: [
+ {
+ id: 'logo_1',
+ position: { x: 0.28, y: 0.96, w: 0.14, h: 0.042 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ {
+ id: 'logo_2',
+ position: { x: 0.72, y: 0.96, w: 0.14, h: 0.042 },
+ anchor: 'bottom-center',
+ style: { objectFit: 'contain', opacity: 0.85 },
+ },
+ ],
+ },
+
+ palette: {
+ primary: '#cd853f',
+ secondary: '#5d4e42',
+ accent: '#daa06d',
+ background: '#1a1410',
+ text: '#faf0e6',
+ textSecondary: '#dcc8b0',
+ textMuted: '#8a7868',
+ },
+
+ typography: {
+ titleFont: 'Playfair Display',
+ headingFont: 'Bebas Neue',
+ bodyFont: 'Open Sans',
+ accentFont: 'Playfair Display',
+ },
+
+ defaultAiPromptHints: {
+ backgroundImage:
+ 'pottery workshop interior, ceramic pots on wooden shelves, warm ambient light, artisan studio atmosphere, rustic terracotta colors, no text',
+ mainImage:
+ 'hands shaping clay on pottery wheel, spinning motion blur, artisan craftsmanship, warm studio light, close-up, no text',
+ },
+
+ metadata: {
+ author: 'System',
+ version: '1.0.0',
+ tags: ['ceramica', 'argilla', 'tornio', 'laboratorio', 'artigianato', 'corso'],
+ isPublic: true,
+ },
+ },
+];
+
+module.exports = templateSeeds;
diff --git a/src/tools/general.js b/src/tools/general.js
index 61e12d2..82f81ee 100755
--- a/src/tools/general.js
+++ b/src/tools/general.js
@@ -1198,6 +1198,7 @@ module.exports = {
let paramsObj = {
usernameDest,
circuitnameDest: circuitname,
+ circuitId: myreccircuit ? myreccircuit._id : ''
path,
username_action: username_action,
singleadmin_username: usernameDest,
@@ -6433,4 +6434,5 @@ module.exports = {
// Usa padding di 3 cifre per minor e patch (supporta fino a 999)
return major * 1000000 + minor * 1000 + patch;
},
+
};
diff --git a/src/tools/globalTables.js b/src/tools/globalTables.js
index 032e39a..309f4f9 100755
--- a/src/tools/globalTables.js
+++ b/src/tools/globalTables.js
@@ -450,6 +450,7 @@ module.exports = {
usernameInvitante: paramsObj.extrarec?.username_admin_abilitante,
nomeTerritorio: paramsObj.circuitnameDest,
link_group: paramsObj.extrarec?.link_group,
+ circuitId: paramsObj.circuitId,
};
await sendemail.sendEmail_Utente_Abilitato_Circuito_FidoConcesso(usertosend.lang, usertosend.email, usertosend, params.idapp, dati);
}
diff --git a/src/tools/shared_nodejs.js b/src/tools/shared_nodejs.js
index 7e99378..cdc35ec 100755
--- a/src/tools/shared_nodejs.js
+++ b/src/tools/shared_nodejs.js
@@ -1283,7 +1283,6 @@ module.exports = {
DASHBOARD: 140,
DASHGROUP: 145,
MOVEMENTS: 148,
- CSENDRISTO: 150,
STATUSREG: 160,
CHECKIFISLOGGED: 170,
INFO_VERSION: 180,
diff --git a/uploads/ai-generated/ai_1765716970209_55yyoiuf2.jpg b/uploads/ai-generated/ai_1765716970209_55yyoiuf2.jpg
new file mode 100644
index 0000000..563c1a0
Binary files /dev/null and b/uploads/ai-generated/ai_1765716970209_55yyoiuf2.jpg differ
diff --git a/uploads/posters/final/poster_693eb441fc62c90831608b91_1765717057875.jpg b/uploads/posters/final/poster_693eb441fc62c90831608b91_1765717057875.jpg
new file mode 100644
index 0000000..858e1b0
Binary files /dev/null and b/uploads/posters/final/poster_693eb441fc62c90831608b91_1765717057875.jpg differ
diff --git a/uploads/posters/final/poster_693eb441fc62c90831608b91_1765717057875.png b/uploads/posters/final/poster_693eb441fc62c90831608b91_1765717057875.png
new file mode 100644
index 0000000..80786e9
Binary files /dev/null and b/uploads/posters/final/poster_693eb441fc62c90831608b91_1765717057875.png differ
diff --git a/uploads/posters/final/poster_693f09610619ba80bec9de2c_1765738849964.jpg b/uploads/posters/final/poster_693f09610619ba80bec9de2c_1765738849964.jpg
new file mode 100644
index 0000000..858e1b0
Binary files /dev/null and b/uploads/posters/final/poster_693f09610619ba80bec9de2c_1765738849964.jpg differ
diff --git a/uploads/posters/final/poster_693f09610619ba80bec9de2c_1765738849964.png b/uploads/posters/final/poster_693f09610619ba80bec9de2c_1765738849964.png
new file mode 100644
index 0000000..80786e9
Binary files /dev/null and b/uploads/posters/final/poster_693f09610619ba80bec9de2c_1765738849964.png differ
diff --git a/yarn.lock b/yarn.lock
index 0ced398..36c6182 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -199,7 +199,7 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz"
integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
-"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9":
+"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.22.5", "@babel/core@^7.23.9", "@babel/core@^7.8.0":
version "7.26.9"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz"
integrity sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==
@@ -486,7 +486,7 @@
stealthy-require "^1.1.1"
tough-cookie "^4.1.3"
-"@cypress/request@^3.0.1":
+"@cypress/request@^3.0.0", "@cypress/request@^3.0.1":
version "3.0.7"
resolved "https://registry.npmjs.org/@cypress/request/-/request-3.0.7.tgz"
integrity sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==
@@ -510,12 +510,14 @@
tunnel-agent "^0.6.0"
uuid "^8.3.2"
-"@emnapi/runtime@^1.2.0":
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.6.0.tgz#8fe297e0090f6e89a57a1f31f1c440bdbc3c01d8"
- integrity sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==
+"@fal-ai/client@^1.7.2":
+ version "1.7.2"
+ resolved "https://registry.npmjs.org/@fal-ai/client/-/client-1.7.2.tgz"
+ integrity sha512-RZ1Qz2Kza4ExKPy2D+2UUWthNApe+oZe8D1Wcxqleyn4F344MOm8ibgqG2JSVmybEcJAD4q44078WYfb6Q9c6w==
dependencies:
- tslib "^2.4.0"
+ "@msgpack/msgpack" "^3.0.0-beta2"
+ eventsource-parser "^1.1.2"
+ robot3 "^0.4.1"
"@fast-csv/format@5.0.5":
version "5.0.5"
@@ -570,112 +572,11 @@
optionalDependencies:
"@img/sharp-libvips-darwin-arm64" "1.0.4"
-"@img/sharp-darwin-x64@0.33.5":
- version "0.33.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61"
- integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==
- optionalDependencies:
- "@img/sharp-libvips-darwin-x64" "1.0.4"
-
"@img/sharp-libvips-darwin-arm64@1.0.4":
version "1.0.4"
resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz"
integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==
-"@img/sharp-libvips-darwin-x64@1.0.4":
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062"
- integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==
-
-"@img/sharp-libvips-linux-arm64@1.0.4":
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704"
- integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==
-
-"@img/sharp-libvips-linux-arm@1.0.5":
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197"
- integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==
-
-"@img/sharp-libvips-linux-s390x@1.0.4":
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce"
- integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==
-
-"@img/sharp-libvips-linux-x64@1.0.4":
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0"
- integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==
-
-"@img/sharp-libvips-linuxmusl-arm64@1.0.4":
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5"
- integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==
-
-"@img/sharp-libvips-linuxmusl-x64@1.0.4":
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff"
- integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==
-
-"@img/sharp-linux-arm64@0.33.5":
- version "0.33.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22"
- integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==
- optionalDependencies:
- "@img/sharp-libvips-linux-arm64" "1.0.4"
-
-"@img/sharp-linux-arm@0.33.5":
- version "0.33.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff"
- integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==
- optionalDependencies:
- "@img/sharp-libvips-linux-arm" "1.0.5"
-
-"@img/sharp-linux-s390x@0.33.5":
- version "0.33.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667"
- integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==
- optionalDependencies:
- "@img/sharp-libvips-linux-s390x" "1.0.4"
-
-"@img/sharp-linux-x64@0.33.5":
- version "0.33.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb"
- integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==
- optionalDependencies:
- "@img/sharp-libvips-linux-x64" "1.0.4"
-
-"@img/sharp-linuxmusl-arm64@0.33.5":
- version "0.33.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b"
- integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==
- optionalDependencies:
- "@img/sharp-libvips-linuxmusl-arm64" "1.0.4"
-
-"@img/sharp-linuxmusl-x64@0.33.5":
- version "0.33.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48"
- integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==
- optionalDependencies:
- "@img/sharp-libvips-linuxmusl-x64" "1.0.4"
-
-"@img/sharp-wasm32@0.33.5":
- version "0.33.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1"
- integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==
- dependencies:
- "@emnapi/runtime" "^1.2.0"
-
-"@img/sharp-win32-ia32@0.33.5":
- version "0.33.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9"
- integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==
-
-"@img/sharp-win32-x64@0.33.5":
- version "0.33.5"
- resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342"
- integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==
-
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz"
@@ -1016,6 +917,11 @@
dependencies:
sparse-bitfield "^3.0.3"
+"@msgpack/msgpack@^3.0.0-beta2":
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.2.tgz"
+ integrity sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ==
+
"@pdf-lib/standard-fonts@^1.0.0":
version "1.0.0"
resolved "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz"
@@ -1182,9 +1088,9 @@
undici-types "~6.20.0"
"@types/node@^18.11.18":
- version "18.19.78"
- resolved "https://registry.npmjs.org/@types/node/-/node-18.19.78.tgz"
- integrity sha512-m1ilZCTwKLkk9rruBJXFeYN0Bc5SbjirwYX/Td3MqPfioYbgun3IvK/m8dQxMCnrPGZPg1kvXjp3SIekCN/ynw==
+ version "18.19.130"
+ resolved "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz"
+ integrity sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==
dependencies:
undici-types "~5.26.4"
@@ -1389,7 +1295,7 @@ ansi-styles@^6.1.0:
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
-ansi-wrap@0.1.0, ansi-wrap@^0.1.0:
+ansi-wrap@^0.1.0, ansi-wrap@0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz"
integrity sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==
@@ -1537,7 +1443,7 @@ assert-never@^1.2.1:
resolved "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz"
integrity sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==
-assert-plus@1.0.0, assert-plus@^1.0.0:
+assert-plus@^1.0.0, assert-plus@1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz"
integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==
@@ -1614,24 +1520,15 @@ aws4@^1.8.0:
resolved "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz"
integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==
-axios@^1.13.0:
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.0.tgz#ead6f495f41f9c8869dcf7b0f24f5a4ab89707f0"
- integrity sha512-zt40Pz4zcRXra9CVV31KeyofwiNvAbJ5B6YPz9pMJ+yOSLikvPT4Yi5LjfgjRa9CawVYBaD1JQzIVcIvBejKeA==
+axios@^1.13.0, axios@^1.7.9:
+ version "1.13.2"
+ resolved "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz"
+ integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.4"
proxy-from-env "^1.1.0"
-axios@^1.7.9:
- version "1.8.1"
- resolved "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz"
- integrity sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==
- dependencies:
- follow-redirects "^1.15.6"
- form-data "^4.0.0"
- proxy-from-env "^1.1.0"
-
b4a@^1.6.4:
version "1.6.7"
resolved "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz"
@@ -1721,7 +1618,7 @@ balanced-match@^1.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-bare-events@^2.2.0, bare-events@^2.5.4:
+bare-events@*, bare-events@^2.2.0, bare-events@^2.5.4:
version "2.5.4"
resolved "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz"
integrity sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==
@@ -1759,7 +1656,7 @@ base64-js@^1.3.1:
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-base64id@2.0.0, base64id@~2.0.0:
+base64id@~2.0.0, base64id@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz"
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
@@ -1776,7 +1673,7 @@ basic-ftp@^5.0.2, basic-ftp@^5.0.5:
resolved "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz"
integrity sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==
-batch@0.6.1, batch@^0.6.1:
+batch@^0.6.1, batch@0.6.1:
version "0.6.1"
resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz"
integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
@@ -1793,16 +1690,16 @@ bcryptjs@^3.0.2:
resolved "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz"
integrity sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==
-bignumber.js@9.0.0:
- version "9.0.0"
- resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz"
- integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==
-
bignumber.js@^2.1.0:
version "2.4.0"
resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz"
integrity sha512-uw4ra6Cv483Op/ebM0GBKKfxZlSmn6NgFRby5L3yGTlunLj53KQgndDlqy2WVFOwgvurocApYkSud0aO+mvrpQ==
+bignumber.js@9.0.0:
+ version "9.0.0"
+ resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz"
+ integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==
+
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
@@ -1835,6 +1732,15 @@ bl@^3.0.0:
dependencies:
readable-stream "^3.0.1"
+bl@^4.0.3:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz"
+ integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
+ dependencies:
+ buffer "^5.5.0"
+ inherits "^2.0.4"
+ readable-stream "^3.4.0"
+
bl@^5.0.0:
version "5.1.0"
resolved "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz"
@@ -1869,7 +1775,7 @@ bn.js@^4.0.0:
resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz"
integrity sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==
-body-parser@1.20.3, body-parser@^1.20.3:
+body-parser@^1.20.3, body-parser@1.20.3:
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==
@@ -1980,7 +1886,7 @@ browser-sync@^3.0.3:
ua-parser-js "^1.0.33"
yargs "^17.3.1"
-browserslist@^4.24.0:
+browserslist@^4.24.0, "browserslist@>= 4.21.0":
version "4.24.4"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz"
integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
@@ -2027,6 +1933,14 @@ buffer-from@^1.0.0:
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+buffer@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz"
+ integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
+ dependencies:
+ base64-js "^1.3.1"
+ ieee754 "^1.1.13"
+
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz"
@@ -2105,6 +2019,14 @@ caniuse-lite@^1.0.30001688:
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz"
integrity sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==
+canvas@^3.0.0, canvas@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/canvas/-/canvas-3.2.0.tgz"
+ integrity sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==
+ dependencies:
+ node-addon-api "^7.0.0"
+ prebuild-install "^7.1.3"
+
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz"
@@ -2133,14 +2055,6 @@ cfb@~1.2.1:
adler-32 "~1.3.0"
crc-32 "~1.2.0"
-chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2:
- version "4.1.2"
- resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
- integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
- dependencies:
- ansi-styles "^4.1.0"
- supports-color "^7.1.0"
-
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz"
@@ -2149,16 +2063,19 @@ chalk@^3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2, chalk@4.1.2:
+ version "4.1.2"
+ resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
char-regex@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
-character-parser@1.2.1:
- version "1.2.1"
- resolved "https://registry.npmjs.org/character-parser/-/character-parser-1.2.1.tgz"
- integrity sha512-6OEBVBlf/y8LaAphnbAnt743O3zMhlBer+FO5D40H6wqAdU9B1TvuApkejgLW0cvv0tEZNLktv1AnRI+C87ueQ==
-
character-parser@^2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz"
@@ -2166,6 +2083,11 @@ character-parser@^2.2.0:
dependencies:
is-regex "^1.0.3"
+character-parser@1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/character-parser/-/character-parser-1.2.1.tgz"
+ integrity sha512-6OEBVBlf/y8LaAphnbAnt743O3zMhlBer+FO5D40H6wqAdU9B1TvuApkejgLW0cvv0tEZNLktv1AnRI+C87ueQ==
+
charenc@0.0.2:
version "0.0.2"
resolved "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz"
@@ -2183,19 +2105,6 @@ cheerio-select@^2.1.0:
domhandler "^5.0.3"
domutils "^3.0.1"
-cheerio@1.0.0-rc.12:
- version "1.0.0-rc.12"
- resolved "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz"
- integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==
- dependencies:
- cheerio-select "^2.1.0"
- dom-serializer "^2.0.0"
- domhandler "^5.0.3"
- domutils "^3.0.1"
- htmlparser2 "^8.0.1"
- parse5 "^7.0.0"
- parse5-htmlparser2-tree-adapter "^7.0.0"
-
cheerio@^0.22.0:
version "0.22.0"
resolved "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz"
@@ -2235,6 +2144,19 @@ cheerio@^1.0.0:
undici "^6.19.5"
whatwg-mimetype "^4.0.0"
+cheerio@1.0.0-rc.12:
+ version "1.0.0-rc.12"
+ resolved "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz"
+ integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==
+ dependencies:
+ cheerio-select "^2.1.0"
+ dom-serializer "^2.0.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+ htmlparser2 "^8.0.1"
+ parse5 "^7.0.0"
+ parse5-htmlparser2-tree-adapter "^7.0.0"
+
chokidar@^3.5.1, chokidar@^3.5.2, chokidar@^3.5.3:
version "3.6.0"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
@@ -2250,7 +2172,7 @@ chokidar@^3.5.1, chokidar@^3.5.2, chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
-chownr@^1.1.4:
+chownr@^1.1.1, chownr@^1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
@@ -2392,13 +2314,6 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
-commander@2.8.x:
- version "2.8.1"
- resolved "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz"
- integrity sha512-+pJLBFVk+9ZZdlAOB5WuIElVPPth47hILFkmGym57aq8kwxsowvByvB0DHs1vQAhyMZzdcpTtF0VDKGkSDR4ZQ==
- dependencies:
- graceful-readlink ">= 1.0.0"
-
commander@^2.2.0:
version "2.20.3"
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
@@ -2414,6 +2329,13 @@ commander@~2.6.0:
resolved "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz"
integrity sha512-PhbTMT+ilDXZKqH8xbvuUY2ZEQNef0Q7DKxgoEKb4ccytsdvVVJmYqR0sGbi96nxU6oGrwEIQnclpK2NBZuQlg==
+commander@2.8.x:
+ version "2.8.1"
+ resolved "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz"
+ integrity sha512-+pJLBFVk+9ZZdlAOB5WuIElVPPth47hILFkmGym57aq8kwxsowvByvB0DHs1vQAhyMZzdcpTtF0VDKGkSDR4ZQ==
+ dependencies:
+ graceful-readlink ">= 1.0.0"
+
component-emitter@^1.3.0:
version "1.3.1"
resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz"
@@ -2507,6 +2429,11 @@ cookie-signature@1.0.6:
resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
+cookie@~0.7.2, cookie@0.7.2:
+ version "0.7.2"
+ resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz"
+ integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
+
cookie@0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz"
@@ -2517,11 +2444,6 @@ cookie@0.7.1:
resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz"
integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
-cookie@0.7.2, cookie@~0.7.2:
- version "0.7.2"
- resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz"
- integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
-
cookiejar@^2.1.4:
version "2.1.4"
resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz"
@@ -2535,16 +2457,16 @@ copy-props@^4.0.0:
each-props "^3.0.0"
is-plain-object "^5.0.0"
-core-util-is@1.0.2:
- version "1.0.2"
- resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
- integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
-
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+core-util-is@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
+ integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
+
cors@^2.8.5, cors@~2.8.5:
version "2.8.5"
resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz"
@@ -2663,16 +2585,16 @@ css-stringify@1.0.5:
resolved "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz"
integrity sha512-aIThpcErhG5EyHorGqNlTh0TduNBqLrrXLO3x5rku3ZKBxuVfY+T7noyM2G2X/01iQANqJUb6d3+FLoa+N7Xwg==
-css-what@2.1:
- version "2.1.3"
- resolved "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz"
- integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
-
css-what@^6.1.0:
version "6.1.0"
resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
+css-what@2.1:
+ version "2.1.3"
+ resolved "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz"
+ integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
+
css@~1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/css/-/css-1.0.8.tgz"
@@ -2751,27 +2673,41 @@ data-view-byte-offset@^1.0.1:
es-errors "^1.3.0"
is-data-view "^1.0.1"
-debug@2.6.9, debug@^2.2.0:
+debug@^2.2.0:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
-debug@4, debug@4.x, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5:
- version "4.4.0"
- resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz"
- integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
- dependencies:
- ms "^2.1.3"
-
-debug@^3.1.0, debug@^3.2.6, debug@^3.2.7:
+debug@^3.1.0:
version "3.2.7"
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
dependencies:
ms "^2.1.1"
+debug@^3.2.6:
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
+ integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
+ dependencies:
+ ms "^2.1.1"
+
+debug@^3.2.7:
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
+ integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
+ dependencies:
+ ms "^2.1.1"
+
+debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@4, debug@4.x:
+ version "4.4.0"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz"
+ integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
+ dependencies:
+ ms "^2.1.3"
+
debug@^4.4.1:
version "4.4.1"
resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz"
@@ -2779,13 +2715,34 @@ debug@^4.4.1:
dependencies:
ms "^2.1.3"
-debug@~4.3.1, debug@~4.3.2, debug@~4.3.4:
+debug@~4.3.1:
version "4.3.7"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies:
ms "^2.1.3"
+debug@~4.3.2:
+ version "4.3.7"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz"
+ integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
+ dependencies:
+ ms "^2.1.3"
+
+debug@~4.3.4:
+ version "4.3.7"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz"
+ integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
+ dependencies:
+ ms "^2.1.3"
+
+debug@2.6.9:
+ version "2.6.9"
+ resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
decamelize@^1.0.0:
version "1.2.0"
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
@@ -2801,6 +2758,13 @@ decimal.js@^10.4.3:
resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz"
integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==
+decompress-response@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz"
+ integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+ dependencies:
+ mimic-response "^3.1.0"
+
dedent@^1.0.0:
version "1.5.3"
resolved "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz"
@@ -2871,16 +2835,16 @@ delegates@^1.0.0:
resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz"
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
-depd@2.0.0, depd@~2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
- integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
-
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz"
integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
+depd@~2.0.0, depd@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
destroy@1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz"
@@ -2901,6 +2865,11 @@ detect-libc@^1.0.3:
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz"
integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
+detect-libc@^2.0.0:
+ version "2.1.2"
+ resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz"
+ integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
+
detect-libc@^2.0.3:
version "2.0.3"
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz"
@@ -2916,7 +2885,7 @@ dev-ip@^1.0.1:
resolved "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz"
integrity sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==
-devtools-protocol@0.0.1439962:
+devtools-protocol@*, devtools-protocol@0.0.1439962:
version "0.0.1439962"
resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1439962.tgz"
integrity sha512-jJF48UdryzKiWhJ1bLKr7BFWUQCEIT5uCNbDLqkQJBtkFxYzILJH44WN0PDKMIlGDN7Utb8vyUY85C3w4R/t2g==
@@ -2967,14 +2936,6 @@ doctypes@^1.1.0:
resolved "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz"
integrity sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==
-dom-serializer@0:
- version "0.2.2"
- resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz"
- integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
- dependencies:
- domelementtype "^2.0.1"
- entities "^2.0.0"
-
dom-serializer@^1.0.1:
version "1.4.1"
resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz"
@@ -3001,12 +2962,20 @@ dom-serializer@~0.1.0:
domelementtype "^1.3.0"
entities "^1.1.1"
+dom-serializer@0:
+ version "0.2.2"
+ resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz"
+ integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
+ dependencies:
+ domelementtype "^2.0.1"
+ entities "^2.0.0"
+
dom-walk@^0.1.0:
version "0.1.2"
resolved "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz"
integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
-domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
+domelementtype@^1.3.0, domelementtype@^1.3.1, domelementtype@1:
version "1.3.1"
resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
@@ -3044,14 +3013,6 @@ domhandler@^5.0.2, domhandler@^5.0.3:
dependencies:
domelementtype "^2.3.0"
-domutils@1.5.1:
- version "1.5.1"
- resolved "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz"
- integrity sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==
- dependencies:
- dom-serializer "0"
- domelementtype "1"
-
domutils@^1.5.1:
version "1.7.0"
resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz"
@@ -3078,7 +3039,15 @@ domutils@^3.0.1, domutils@^3.1.0:
domelementtype "^2.3.0"
domhandler "^5.0.3"
-dotenv@16.4.7, dotenv@^16.4.7:
+domutils@1.5.1:
+ version "1.5.1"
+ resolved "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz"
+ integrity sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==
+ dependencies:
+ dom-serializer "0"
+ domelementtype "1"
+
+dotenv@^16.4.7, dotenv@16.4.7:
version "16.4.7"
resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz"
integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==
@@ -3144,7 +3113,7 @@ ee-first@1.1.1:
resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
-ejs@^3.1.10:
+ejs@^3.1.10, ejs@^3.1.5:
version "3.1.10"
resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz"
integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==
@@ -3169,7 +3138,7 @@ email-templates@^12.0.2:
lodash "^4.17.21"
nodemailer "^6.9.14"
optionalDependencies:
- preview-email "^3.0.17"
+ preview-email "*"
emittery@^0.13.1:
version "0.13.1"
@@ -3191,7 +3160,12 @@ emojilib@^2.4.0:
resolved "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz"
integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==
-encodeurl@~1.0.1, encodeurl@~1.0.2:
+encodeurl@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
+ integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
+
+encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
@@ -3214,7 +3188,7 @@ encoding-sniffer@^0.2.0:
iconv-lite "^0.6.3"
whatwg-encoding "^3.1.1"
-end-of-stream@^1.1.0, end-of-stream@^1.4.4:
+end-of-stream@^1.1.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4:
version "1.4.4"
resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
@@ -3262,7 +3236,17 @@ entities@^2.0.0:
resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
-entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
+entities@^4.2.0:
+ version "4.5.0"
+ resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz"
+ integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+
+entities@^4.4.0:
+ version "4.5.0"
+ resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz"
+ integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+
+entities@^4.5.0:
version "4.5.0"
resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
@@ -3274,7 +3258,7 @@ entities@^6.0.0:
entities@^7.0.0:
version "7.0.0"
- resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.0.tgz#2ae4e443f3f17d152d3f5b0f79b932c1e59deb7a"
+ resolved "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz"
integrity sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==
env-paths@^2.2.1:
@@ -3470,7 +3454,7 @@ esutils@^2.0.2:
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
-etag@1.8.1, etag@^1.8.1, etag@~1.8.1:
+etag@^1.8.1, etag@~1.8.1, etag@1.8.1:
version "1.8.1"
resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
@@ -3508,6 +3492,11 @@ events@^3.0.0, events@^3.3.0:
resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
+eventsource-parser@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz"
+ integrity sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==
+
execa@^0.10.0:
version "0.10.0"
resolved "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz"
@@ -3546,6 +3535,11 @@ exit@^0.1.2:
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
+expand-template@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz"
+ integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
+
expand-tilde@^2.0.0, expand-tilde@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz"
@@ -3564,7 +3558,12 @@ expect@^29.7.0:
jest-message-util "^29.7.0"
jest-util "^29.7.0"
-express@^4.21.2:
+express-rate-limit@^7.1.5:
+ version "7.5.1"
+ resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz"
+ integrity sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==
+
+express@^4.21.2, "express@>= 4.11":
version "4.21.2"
resolved "https://registry.npmjs.org/express/-/express-4.21.2.tgz"
integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==
@@ -3640,16 +3639,16 @@ extract-zip@^2.0.1:
optionalDependencies:
"@types/yauzl" "^2.9.1"
-extsprintf@1.3.0:
- version "1.3.0"
- resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz"
- integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==
-
extsprintf@^1.2.0:
version "1.4.1"
resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz"
integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
+extsprintf@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz"
+ integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==
+
fancy-log@^1.3.3:
version "1.3.3"
resolved "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz"
@@ -3882,20 +3881,10 @@ form-data-encoder@1.7.2:
resolved "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz"
integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==
-form-data@^4.0.0, form-data@^4.0.1, form-data@~4.0.0:
- version "4.0.2"
- resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz"
- integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.8"
- es-set-tostringtag "^2.1.0"
- mime-types "^2.1.12"
-
-form-data@^4.0.4:
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4"
- integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==
+form-data@^4.0.0, form-data@^4.0.1, form-data@^4.0.4, form-data@~4.0.0:
+ version "4.0.5"
+ resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz"
+ integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
@@ -3939,7 +3928,7 @@ frac@~1.1.2:
resolved "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz"
integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==
-fresh@0.5.2, fresh@^0.5.2:
+fresh@^0.5.2, fresh@0.5.2:
version "0.5.2"
resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
@@ -3949,6 +3938,20 @@ from@^0.1.7:
resolved "https://registry.npmjs.org/from/-/from-0.1.7.tgz"
integrity sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==
+fs-constants@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz"
+ integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+
+fs-extra@^11.3.2:
+ version "11.3.2"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz"
+ integrity sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
fs-extra@3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz"
@@ -3958,15 +3961,6 @@ fs-extra@3.0.1:
jsonfile "^3.0.0"
universalify "^0.1.0"
-fs-extra@^11.3.2:
- version "11.3.2"
- resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.2.tgz#c838aeddc6f4a8c74dd15f85e11fe5511bfe02a4"
- integrity sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==
- dependencies:
- graceful-fs "^4.2.0"
- jsonfile "^6.0.1"
- universalify "^2.0.0"
-
fs-minipass@^1.2.7:
version "1.2.7"
resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz"
@@ -4129,6 +4123,11 @@ ghostscript4js@^3.2.3:
bindings "^1.5.0"
node-addon-api "^2.0.0"
+github-from-package@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz"
+ integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
+
glob-parent@^6.0.2:
version "6.0.2"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz"
@@ -4252,6 +4251,19 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10,
resolved "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
integrity sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==
+groq-sdk@^0.37.0:
+ version "0.37.0"
+ resolved "https://registry.npmjs.org/groq-sdk/-/groq-sdk-0.37.0.tgz"
+ integrity sha512-lT72pcT8b/X5XrzdKf+rWVzUGW1OQSKESmL8fFN5cTbsf02gq6oFam4SVeNtzELt9cYE2Pt3pdGgSImuTbHFDg==
+ dependencies:
+ "@types/node" "^18.11.18"
+ "@types/node-fetch" "^2.6.4"
+ abort-controller "^3.0.0"
+ agentkeepalive "^4.2.1"
+ form-data-encoder "1.7.2"
+ formdata-node "^4.3.2"
+ node-fetch "^2.6.7"
+
gulp-cli@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.0.0.tgz"
@@ -4402,7 +4414,7 @@ hasown@^2.0.2:
dependencies:
function-bind "^1.1.2"
-he@1.2.0, he@^1.2.0:
+he@^1.2.0, he@1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@@ -4443,7 +4455,7 @@ html-escaper@^2.0.0:
resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
-html-to-text@9.0.5, html-to-text@^9.0.5:
+html-to-text@^9.0.5, html-to-text@9.0.5:
version "9.0.5"
resolved "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz"
integrity sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==
@@ -4496,16 +4508,10 @@ htmlparser2@^9.1.0:
domutils "^3.1.0"
entities "^4.5.0"
-http-errors@2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz"
- integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
- dependencies:
- depd "2.0.0"
- inherits "2.0.4"
- setprototypeof "1.2.0"
- statuses "2.0.1"
- toidentifier "1.0.1"
+http_ece@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/http_ece/-/http_ece-1.2.0.tgz"
+ integrity sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==
http-errors@~1.6.2:
version "1.6.3"
@@ -4528,6 +4534,17 @@ http-errors@~1.7.3:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
+http-errors@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz"
+ integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+ dependencies:
+ depd "2.0.0"
+ inherits "2.0.4"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ toidentifier "1.0.1"
+
http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1, http-proxy-agent@^7.0.2:
version "7.0.2"
resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz"
@@ -4563,11 +4580,6 @@ http-signature@~1.4.0:
jsprim "^2.0.2"
sshpk "^1.18.0"
-http_ece@1.2.0:
- version "1.2.0"
- resolved "https://registry.npmjs.org/http_ece/-/http_ece-1.2.0.tgz"
- integrity sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==
-
https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.6:
version "7.0.6"
resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz"
@@ -4607,21 +4619,28 @@ i18n@^0.15.0, i18n@^0.15.1:
math-interval-parser "^2.0.1"
mustache "^4.2.0"
-iconv-lite@0.4.24, iconv-lite@^0.4.4:
+iconv-lite@^0.4.4:
version "0.4.24"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
-iconv-lite@0.6.3, iconv-lite@^0.6.3:
+iconv-lite@^0.6.3, iconv-lite@0.6.3:
version "0.6.3"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
-ieee754@^1.2.1:
+iconv-lite@0.4.24:
+ version "0.4.24"
+ resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz"
+ integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
+ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@@ -4684,7 +4703,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
+inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@2, inherits@2.0.4:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -5449,7 +5468,7 @@ jest-resolve-dependencies@^29.7.0:
jest-regex-util "^29.6.3"
jest-snapshot "^29.7.0"
-jest-resolve@^29.7.0:
+jest-resolve@*, jest-resolve@^29.7.0:
version "29.7.0"
resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz"
integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==
@@ -5660,16 +5679,16 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
-jsbn@1.1.0:
- version "1.1.0"
- resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz"
- integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
-
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz"
integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
+jsbn@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz"
+ integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
+
jsdom@^26.0.0:
version "26.0.0"
resolved "https://registry.npmjs.org/jsdom/-/jsdom-26.0.0.tgz"
@@ -5736,7 +5755,7 @@ jsonfile@^3.0.0:
jsonfile@^6.0.1:
version "6.2.0"
- resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62"
+ resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz"
integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==
dependencies:
universalify "^2.0.0"
@@ -6108,7 +6127,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz"
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
-lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21:
+lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@4.17.21:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -6273,12 +6292,17 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24,
dependencies:
mime-db "1.52.0"
-mime@1.6.0, mime@^1.3.4, mime@^1.6.0:
+mime@^1.3.4, mime@^1.6.0, mime@1.6.0:
version "1.6.0"
resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
-mime@2.6.0, mime@^2.4.6:
+mime@^2.4.6:
+ version "2.6.0"
+ resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz"
+ integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
+
+mime@2.6.0:
version "2.6.0"
resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz"
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
@@ -6288,6 +6312,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+mimic-response@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz"
+ integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
min-document@^2.19.0:
version "2.19.0"
resolved "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz"
@@ -6312,7 +6341,14 @@ minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
-minimatch@^5.0.1, minimatch@^5.1.6:
+minimatch@^5.0.1:
+ version "5.1.6"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz"
+ integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@^5.1.6:
version "5.1.6"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
@@ -6326,16 +6362,16 @@ minimatch@^9.0.4:
dependencies:
brace-expansion "^2.0.1"
+minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
+ version "1.2.8"
+ resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
integrity sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==
-minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
- version "1.2.8"
- resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
- integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
-
minipass@^2.6.0, minipass@^2.9.0:
version "2.9.0"
resolved "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz"
@@ -6344,7 +6380,12 @@ minipass@^2.6.0, minipass@^2.9.0:
safe-buffer "^5.1.2"
yallist "^3.0.0"
-"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2:
+"minipass@^5.0.0 || ^6.0.2 || ^7.0.0":
+ version "7.1.2"
+ resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
+ integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
+
+minipass@^7.1.2:
version "7.1.2"
resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
@@ -6366,12 +6407,10 @@ mitt@^3.0.1:
resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz"
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
-mkdirp@0.5.1:
- version "0.5.1"
- resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
- integrity sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==
- dependencies:
- minimist "0.0.8"
+mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
+ version "0.5.3"
+ resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz"
+ integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.0:
version "0.5.6"
@@ -6380,6 +6419,13 @@ mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.0:
dependencies:
minimist "^1.2.6"
+mkdirp@0.5.1:
+ version "0.5.1"
+ resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
+ integrity sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==
+ dependencies:
+ minimist "0.0.8"
+
mocha@^11.1.0:
version "11.1.0"
resolved "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz"
@@ -6473,16 +6519,16 @@ mquery@5.0.0:
dependencies:
debug "4.x"
+ms@^2.0.0, ms@^2.1.1, ms@^2.1.3, ms@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
ms@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
-ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3:
- version "2.1.3"
- resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
- integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-
multer@^1.4.5-lts.2:
version "1.4.5-lts.2"
resolved "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz"
@@ -6507,7 +6553,7 @@ multimatch@5:
arrify "^2.0.1"
minimatch "^3.0.4"
-mustache@^4.2.0:
+mustache@^4.0.1, mustache@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz"
integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
@@ -6537,6 +6583,11 @@ nanoid@^3.3.8:
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz"
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
+napi-build-utils@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz"
+ integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
+
native-duplexpair@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz"
@@ -6571,11 +6622,23 @@ nice-try@^1.0.4:
resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
+node-abi@^3.3.0:
+ version "3.85.0"
+ resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz"
+ integrity sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==
+ dependencies:
+ semver "^7.3.5"
+
node-addon-api@^2.0.0:
version "2.0.2"
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz"
integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==
+node-addon-api@^7.0.0:
+ version "7.1.1"
+ resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz"
+ integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
+
node-cron@^3.0.3:
version "3.0.3"
resolved "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz"
@@ -6666,16 +6729,16 @@ node-telegram-bot-api@^0.66.0:
mime "^1.6.0"
pump "^2.0.0"
-nodemailer@6.9.16:
- version "6.9.16"
- resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz"
- integrity sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==
-
nodemailer@^6.10.0, nodemailer@^6.9.13, nodemailer@^6.9.14:
version "6.10.0"
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz"
integrity sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==
+nodemailer@6.9.16:
+ version "6.9.16"
+ resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz"
+ integrity sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==
+
nodemon@^3.1.9:
version "3.1.9"
resolved "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz"
@@ -6700,7 +6763,7 @@ nopt@^4.0.3:
abbrev "1"
osenv "^0.1.4"
-normalize-path@3.0.0, normalize-path@^3.0.0, normalize-path@~3.0.0:
+normalize-path@^3.0.0, normalize-path@~3.0.0, normalize-path@3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
@@ -6835,13 +6898,6 @@ object.pick@^1.3.0:
dependencies:
isobject "^3.0.1"
-on-finished@2.4.1:
- version "2.4.1"
- resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz"
- integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
- dependencies:
- ee-first "1.1.1"
-
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz"
@@ -6849,6 +6905,13 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
+on-finished@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
on-headers@~1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz"
@@ -6868,14 +6931,6 @@ onetime@^5.1.2:
dependencies:
mimic-fn "^2.1.0"
-open@7:
- version "7.4.2"
- resolved "https://registry.npmjs.org/open/-/open-7.4.2.tgz"
- integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
- dependencies:
- is-docker "^2.0.0"
- is-wsl "^2.1.1"
-
open@^10.1.0:
version "10.1.0"
resolved "https://registry.npmjs.org/open/-/open-10.1.0.tgz"
@@ -6886,10 +6941,18 @@ open@^10.1.0:
is-inside-container "^1.0.0"
is-wsl "^3.1.0"
-openai@^4.86.2:
- version "4.87.3"
- resolved "https://registry.npmjs.org/openai/-/openai-4.87.3.tgz"
- integrity sha512-d2D54fzMuBYTxMW8wcNmhT1rYKcTfMJ8t+4KjH2KtvYenygITiGBgHoIrzHwnDQWW+C5oCA+ikIR2jgPCFqcKQ==
+open@7:
+ version "7.4.2"
+ resolved "https://registry.npmjs.org/open/-/open-7.4.2.tgz"
+ integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
+ dependencies:
+ is-docker "^2.0.0"
+ is-wsl "^2.1.1"
+
+openai@^4.104.0:
+ version "4.104.0"
+ resolved "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz"
+ integrity sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==
dependencies:
"@types/node" "^18.11.18"
"@types/node-fetch" "^2.6.4"
@@ -7152,7 +7215,12 @@ path-key@^2.0.0, path-key@^2.0.1:
resolved "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz"
integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
-path-key@^3.0.0, path-key@^3.1.0:
+path-key@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
@@ -7322,6 +7390,24 @@ postcss@^8.3.11:
picocolors "^1.1.1"
source-map-js "^1.2.1"
+prebuild-install@^7.1.3:
+ version "7.1.3"
+ resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz"
+ integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
+ dependencies:
+ detect-libc "^2.0.0"
+ expand-template "^2.0.3"
+ github-from-package "0.0.0"
+ minimist "^1.2.3"
+ mkdirp-classic "^0.5.3"
+ napi-build-utils "^2.0.0"
+ node-abi "^3.3.0"
+ pump "^3.0.0"
+ rc "^1.2.7"
+ simple-get "^4.0.0"
+ tar-fs "^2.0.0"
+ tunnel-agent "^0.6.0"
+
pretty-format@^29.7.0:
version "29.7.0"
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz"
@@ -7331,9 +7417,9 @@ pretty-format@^29.7.0:
ansi-styles "^5.0.0"
react-is "^18.0.0"
-preview-email@^3.0.17, preview-email@^3.1.0:
+preview-email@*, preview-email@^3.1.0:
version "3.1.0"
- resolved "https://registry.yarnpkg.com/preview-email/-/preview-email-3.1.0.tgz#ee8525d878afef4309ae548116e4a4fe8b119a6d"
+ resolved "https://registry.npmjs.org/preview-email/-/preview-email-3.1.0.tgz"
integrity sha512-ZtV1YrwscEjlrUzYrTSs6Nwo49JM3pXLM4fFOBSC3wSni+bxaWlw9/Qgk75PZO8M7cX2EybmL2iwvaV3vkAttw==
dependencies:
ci-info "^3.8.0"
@@ -7520,7 +7606,7 @@ pug-walk@^2.0.0:
resolved "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz"
integrity sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==
-pug@^3.0.2, pug@^3.0.3:
+pug@^3.0.0, pug@^3.0.2, pug@^3.0.3:
version "3.0.3"
resolved "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz"
integrity sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==
@@ -7589,6 +7675,18 @@ pure-rand@^6.0.0:
resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz"
integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==
+qs@^6.11.0:
+ version "6.14.0"
+ resolved "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz"
+ integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
+ dependencies:
+ side-channel "^1.1.0"
+
+qs@~6.5.2:
+ version "6.5.3"
+ resolved "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz"
+ integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
+
qs@6.13.0:
version "6.13.0"
resolved "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz"
@@ -7603,18 +7701,6 @@ qs@6.13.1:
dependencies:
side-channel "^1.0.6"
-qs@^6.11.0:
- version "6.14.0"
- resolved "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz"
- integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
- dependencies:
- side-channel "^1.1.0"
-
-qs@~6.5.2:
- version "6.5.3"
- resolved "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz"
- integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
-
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz"
@@ -7642,7 +7728,7 @@ rate-limiter-flexible@^5.0.5:
resolved "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-5.0.5.tgz"
integrity sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==
-raw-body@2.5.2, raw-body@^2.3.2:
+raw-body@^2.3.2, raw-body@2.5.2:
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==
@@ -7652,7 +7738,7 @@ raw-body@2.5.2, raw-body@^2.3.2:
iconv-lite "0.4.24"
unpipe "1.0.0"
-rc@^1.2.8:
+rc@^1.2.7, rc@^1.2.8:
version "1.2.8"
resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
@@ -7672,28 +7758,6 @@ read-chunk@^1.0.1:
resolved "https://registry.npmjs.org/read-chunk/-/read-chunk-1.0.1.tgz"
integrity sha512-5NLTTdX45dKFtG8CX5pKmvS9V5u9wBE+gkklN7xhDuhq3pA2I4O7ALfKxosCMcLHOhkxj6GNacZhfXtp5nlCdg==
-readable-stream@2.3.7:
- version "2.3.7"
- resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
- integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
- 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"
-
-readable-stream@3, readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.3.0, readable-stream@^3.4.0:
- version "3.6.2"
- resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
- integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
- dependencies:
- inherits "^2.0.3"
- string_decoder "^1.1.1"
- util-deprecate "^1.0.1"
-
readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.5:
version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
@@ -7707,6 +7771,33 @@ readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2, readable
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
+readable-stream@^3.0.1, readable-stream@^3.3.0:
+ version "3.6.2"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
+ integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+readable-stream@^3.1.1, readable-stream@^3.4.0:
+ version "3.6.2"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
+ integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+readable-stream@^3.4.0:
+ version "3.6.2"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
+ integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
readable-stream@^4.2.0:
version "4.7.0"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz"
@@ -7718,6 +7809,39 @@ readable-stream@^4.2.0:
process "^0.11.10"
string_decoder "^1.3.0"
+readable-stream@>=4.0.0:
+ version "4.7.0"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz"
+ integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==
+ dependencies:
+ abort-controller "^3.0.0"
+ buffer "^6.0.3"
+ events "^3.3.0"
+ process "^0.11.10"
+ string_decoder "^1.3.0"
+
+readable-stream@2.3.7:
+ version "2.3.7"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
+ integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+ 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"
+
+readable-stream@3:
+ version "3.6.2"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
+ integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
@@ -7800,6 +7924,13 @@ replacestream@^4.0.3:
object-assign "^4.0.1"
readable-stream "^2.0.2"
+replicate@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/replicate/-/replicate-1.4.0.tgz"
+ integrity sha512-1ufKejfUVz/azy+5TnzQP7U1+MHVWZ6psnQ06az8byUUnRhT+DZ/MvewzB1NQYBVMgNKR7xPDtTwlcP5nv/5+w==
+ optionalDependencies:
+ readable-stream ">=4.0.0"
+
request-promise-core@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz"
@@ -7807,7 +7938,7 @@ request-promise-core@1.1.3:
dependencies:
lodash "^4.17.15"
-request@^2.65.0, request@^2.88:
+request@^2.34, request@^2.65.0, request@^2.88:
version "2.88.2"
resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -7921,6 +8052,11 @@ rndm@1.2.0:
resolved "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz"
integrity sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==
+robot3@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.npmjs.org/robot3/-/robot3-0.4.1.tgz"
+ integrity sha512-hzjy826lrxzx8eRgv80idkf8ua1JAepRc9Efdtj03N3KNJuznQCPlyCJ7gnUmDFwZCLQjxy567mQVKmdv2BsXQ==
+
rrweb-cssom@^0.8.0:
version "0.8.0"
resolved "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz"
@@ -7954,15 +8090,20 @@ safe-array-concat@^1.1.3:
has-symbols "^1.1.0"
isarray "^2.0.5"
-safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0, safe-buffer@5.2.1:
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+safe-buffer@~5.1.0:
version "5.1.2"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
- version "5.2.1"
- resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
- integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+safe-buffer@~5.1.1, safe-buffer@5.1.2:
+ version "5.1.2"
+ resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-identifier@^0.4.1:
version "0.4.2"
@@ -7986,7 +8127,7 @@ safe-regex-test@^1.1.0:
es-errors "^1.3.0"
is-regex "^1.2.1"
-"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
+safer-buffer@^2.0.2, safer-buffer@^2.1.0, "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -8013,7 +8154,7 @@ save@^2.9.0:
lodash.assign "^4.2.0"
mingo "^6.1.0"
-sax@>=0.6.0, sax@^1.2.4:
+sax@^1.2.4, sax@>=0.6.0:
version "1.4.1"
resolved "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz"
integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
@@ -8039,17 +8180,27 @@ semver-greatest-satisfied-range@^2.0.0:
dependencies:
sver "^1.8.3"
-semver@^5.5.0, semver@^5.7.1:
+semver@^5.5.0:
version "5.7.2"
resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
-semver@^6.3.0, semver@^6.3.1:
+semver@^5.7.1:
+ version "5.7.2"
+ resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz"
+ integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
+
+semver@^6.3.0:
version "6.3.1"
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.5.3, semver@^7.5.4, semver@^7.6.3:
+semver@^6.3.1:
+ version "6.3.1"
+ resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+
+semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3:
version "7.7.1"
resolved "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz"
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
@@ -8059,25 +8210,6 @@ semver@^7.7.2:
resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz"
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
-send@0.19.0:
- version "0.19.0"
- resolved "https://registry.npmjs.org/send/-/send-0.19.0.tgz"
- integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
- 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"
-
send@^0.19.0:
version "0.19.1"
resolved "https://registry.npmjs.org/send/-/send-0.19.1.tgz"
@@ -8097,6 +8229,25 @@ send@^0.19.0:
range-parser "~1.2.1"
statuses "2.0.1"
+send@0.19.0:
+ version "0.19.0"
+ resolved "https://registry.npmjs.org/send/-/send-0.19.0.tgz"
+ integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
+ 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"
+
serialize-javascript@^6.0.2:
version "6.0.2"
resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz"
@@ -8117,7 +8268,7 @@ serve-index@^1.9.1:
mime-types "~2.1.17"
parseurl "~1.3.2"
-serve-static@1.16.2, serve-static@^1.16.2:
+serve-static@^1.16.2, serve-static@1.16.2:
version "1.16.2"
resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz"
integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
@@ -8291,6 +8442,20 @@ signal-exit@^4.0.1:
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+simple-concat@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz"
+ integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
+
+simple-get@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz"
+ integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
+ dependencies:
+ decompress-response "^6.0.0"
+ once "^1.3.1"
+ simple-concat "^1.0.0"
+
simple-swizzle@^0.2.2:
version "0.2.2"
resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz"
@@ -8380,7 +8545,7 @@ socks-proxy-agent@^8.0.5:
debug "^4.3.4"
socks "^2.8.3"
-socks@^2.8.3:
+socks@^2.7.1, socks@^2.8.3:
version "2.8.4"
resolved "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz"
integrity sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==
@@ -8401,14 +8566,7 @@ source-map-support@0.5.13:
buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map@0.4.x:
- version "0.4.4"
- resolved "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz"
- integrity sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==
- dependencies:
- amdefine ">=0.0.4"
-
-source-map@^0.5.1, source-map@~0.5.1:
+source-map@^0.5.1:
version "0.5.7"
resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz"
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
@@ -8425,6 +8583,18 @@ source-map@~0.1.7:
dependencies:
amdefine ">=0.0.4"
+source-map@~0.5.1:
+ version "0.5.7"
+ resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz"
+ integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
+
+source-map@0.4.x:
+ version "0.4.4"
+ resolved "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz"
+ integrity sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==
+ dependencies:
+ amdefine ">=0.0.4"
+
sparkles@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz"
@@ -8493,12 +8663,12 @@ stack-utils@^2.0.3:
dependencies:
escape-string-regexp "^2.0.0"
-statuses@2.0.1:
- version "2.0.1"
- resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
- integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+"statuses@>= 1.4.0 < 2":
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
+ integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
-"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2":
+"statuses@>= 1.5.0 < 2":
version "1.5.0"
resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
@@ -8508,6 +8678,11 @@ statuses@~1.3.1:
resolved "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz"
integrity sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg==
+statuses@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
+ integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
stealthy-require@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz"
@@ -8573,6 +8748,20 @@ streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.2, streamx@^2.14.0, streamx@^2.1
optionalDependencies:
bare-events "^2.2.0"
+string_decoder@^1.1.1, string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+string_decoder@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
string-length@^4.0.1:
version "4.0.2"
resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz"
@@ -8649,20 +8838,6 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
-string_decoder@^1.1.1, string_decoder@^1.3.0:
- version "1.3.0"
- resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
- integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
- dependencies:
- safe-buffer "~5.2.0"
-
-string_decoder@~1.1.1:
- version "1.1.1"
- resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
- integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
- dependencies:
- safe-buffer "~5.1.0"
-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
@@ -8777,7 +8952,14 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
-supports-color@^8.0.0, supports-color@^8.1.1:
+supports-color@^8.0.0:
+ version "8.1.1"
+ resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-color@^8.1.1:
version "8.1.1"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
@@ -8801,6 +8983,16 @@ symbol-tree@^3.2.4:
resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+tar-fs@^2.0.0:
+ version "2.1.4"
+ resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz"
+ integrity sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==
+ dependencies:
+ chownr "^1.1.1"
+ mkdirp-classic "^0.5.2"
+ pump "^3.0.0"
+ tar-stream "^2.1.4"
+
tar-fs@^3.0.8:
version "3.0.9"
resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz"
@@ -8812,6 +9004,17 @@ tar-fs@^3.0.8:
bare-fs "^4.0.1"
bare-path "^3.0.0"
+tar-stream@^2.1.4:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz"
+ integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
+ dependencies:
+ bl "^4.0.3"
+ end-of-stream "^1.4.1"
+ fs-constants "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^3.1.1"
+
tar-stream@^3.1.5:
version "3.1.7"
resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz"
@@ -8878,6 +9081,11 @@ textextensions@^3.2.0:
resolved "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz"
integrity sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==
+through@^2.3.8, through@~2.3, through@~2.3.4, through@2:
+ version "2.3.8"
+ resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+ integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
+
through2@^4.0.2:
version "4.0.2"
resolved "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz"
@@ -8885,11 +9093,6 @@ through2@^4.0.2:
dependencies:
readable-stream "3"
-through@2, through@^2.3.8, through@~2.3, through@~2.3.4:
- version "2.3.8"
- resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
- integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
-
time-stamp@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz"
@@ -8905,16 +9108,16 @@ titleize@2:
resolved "https://registry.npmjs.org/titleize/-/titleize-2.1.0.tgz"
integrity sha512-m+apkYlfiQTKLW+sI4vqUkwMEzfgEUEYSqljx1voUE3Wz/z1ZsxyzSxvH2X8uKVrOp7QkByWt0rA6+gvhCKy6g==
-tlds@1.255.0:
- version "1.255.0"
- resolved "https://registry.npmjs.org/tlds/-/tlds-1.255.0.tgz"
- integrity sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==
-
tlds@^1.231.0:
version "1.256.0"
resolved "https://registry.npmjs.org/tlds/-/tlds-1.256.0.tgz"
integrity sha512-ZmyVB9DAw+FFTmLElGYJgdZFsKLYd/I59Bg9NHkCGPwAbVZNRilFWDMAdX8UG+bHuv7kfursd5XGqo/9wi26lA==
+tlds@1.255.0:
+ version "1.255.0"
+ resolved "https://registry.npmjs.org/tlds/-/tlds-1.255.0.tgz"
+ integrity sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==
+
tldts-core@^6.1.82:
version "6.1.82"
resolved "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.82.tgz"
@@ -9017,7 +9220,7 @@ tslib@^1.11.1:
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.1, tslib@^2.2.0, tslib@^2.4.0, tslib@^2.6.2:
+tslib@^2.0.1, tslib@^2.2.0, tslib@^2.6.2:
version "2.8.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
@@ -9177,7 +9380,7 @@ underscore.deep@~0.5.1:
resolved "https://registry.npmjs.org/underscore.deep/-/underscore.deep-0.5.3.tgz"
integrity sha512-4OuSOlFNkiVFVc3khkeG112Pdu1gbitMj7t9B9ENb61uFmN70Jq7Iluhi3oflcSgexkKfDdJ5XAJET2gEq6ikA==
-underscore@~1.13.1:
+underscore@^1.11.0, underscore@~1.13.1, underscore@1.x:
version "1.13.7"
resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz"
integrity sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==
@@ -9229,10 +9432,10 @@ universalify@^0.2.0:
universalify@^2.0.0:
version "2.0.1"
- resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
+ resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
-unpipe@1.0.0, unpipe@~1.0.0:
+unpipe@~1.0.0, unpipe@1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
@@ -9277,16 +9480,16 @@ utils-merge@1.0.1:
resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
-uuid@8.3.2, uuid@^8.3.0, uuid@^8.3.2:
- version "8.3.2"
- resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
- integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
-
uuid@^3.3.2:
version "3.4.0"
resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+uuid@^8.3.0, uuid@^8.3.2, uuid@8.3.2:
+ version "8.3.2"
+ resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
uuid@^9.0.1:
version "9.0.1"
resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz"
@@ -9568,7 +9771,14 @@ which@^1.2.14, which@^1.2.9:
dependencies:
isexe "^2.0.0"
-which@^2.0.1, which@^2.0.2:
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+which@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
@@ -9615,16 +9825,16 @@ word@~0.3.0:
resolved "https://registry.npmjs.org/word/-/word-0.3.0.tgz"
integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
-wordwrap@0.0.2:
- version "0.0.2"
- resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
- integrity sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==
-
wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
integrity sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==
+wordwrap@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
+ integrity sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==
+
workerpool@^6.5.1:
version "6.5.1"
resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz"
@@ -9764,16 +9974,16 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1:
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
-yargs-parser@>=5.0.0-security.0, yargs-parser@^21.1.1:
- version "21.1.1"
- resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
- integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
-
yargs-parser@^20.2.2:
version "20.2.9"
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+yargs-parser@^21.1.1, yargs-parser@>=5.0.0-security.0:
+ version "21.1.1"
+ resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
yargs-unparser@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz"
@@ -9833,7 +10043,7 @@ yocto-queue@^0.1.0:
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
-zod@^3.24.1:
+zod@^3.23.8, zod@^3.24.1:
version "3.25.32"
resolved "https://registry.npmjs.org/zod/-/zod-3.25.32.tgz"
integrity sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g==