- aggiornamento di tante cose...
- generazione Volantini - pagina RIS
This commit is contained in:
137
src/models/Asset.js
Normal file
137
src/models/Asset.js
Normal file
@@ -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);
|
||||
262
src/models/Poster.js
Normal file
262
src/models/Poster.js
Normal file
@@ -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);
|
||||
253
src/models/Template.js
Normal file
253
src/models/Template.js
Normal file
@@ -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);
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -106,6 +106,8 @@ MovementSchema.statics.addMov = async function (
|
||||
idOrdersCart
|
||||
) {
|
||||
try {
|
||||
const { Circuit } = require('./circuit');
|
||||
|
||||
// Only positive values
|
||||
amount = Math.abs(amount);
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user