262 lines
7.4 KiB
JavaScript
262 lines
7.4 KiB
JavaScript
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); |