- aggiornamento di tante cose...
- generazione Volantini - pagina RIS
This commit is contained in:
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);
|
||||
Reference in New Issue
Block a user