ai-wpa/templates/model.template.ts

343 lines
7.9 KiB
TypeScript

/**
* Database Model Template
*
* Usage: Copy this file to create new database models
* 1. Replace TEMPLATE_NAME with your model name
* 2. Define the Zod schema for validation
* 3. Create the Mongoose schema
* 4. Add methods and statics as needed
*/
import mongoose, { Document, Model } from 'mongoose'
import { z } from 'zod'
// Zod schema for validation
export const TemplateSchema = z.object({
// Define your fields here
name: z.string().min(1, 'Name is required').max(100, 'Name too long'),
description: z.string().optional(),
category: z.enum(['type1', 'type2', 'type3']).default('type1'),
tags: z.array(z.string()).optional(),
isActive: z.boolean().optional().default(true),
metadata: z.record(z.string(), z.any()).optional(),
// Relationships
userId: z.string().min(1, 'User ID is required'),
parentId: z.string().optional(),
// Nested objects
settings: z
.object({
visibility: z.enum(['public', 'private', 'unlisted']).default('public'),
allowComments: z.boolean().default(true),
featured: z.boolean().default(false),
})
.optional(),
// Arrays of objects
items: z
.array(
z.object({
title: z.string(),
value: z.union([z.string(), z.number()]),
order: z.number().default(0),
})
)
.optional(),
})
// Infer TypeScript type from Zod schema
export type TemplateType = z.infer<typeof TemplateSchema>
// Mongoose document interface
export interface ITemplate extends Document {
name: string
description?: string
category: 'type1' | 'type2' | 'type3'
tags?: string[]
isActive: boolean
metadata?: Record<string, any>
// Relationships
userId: string
parentId?: string
// Nested objects
settings?: {
visibility: 'public' | 'private' | 'unlisted'
allowComments: boolean
featured: boolean
}
// Arrays of objects
items?: Array<{
title: string
value: string | number
order: number
}>
// Timestamps (added by Mongoose)
createdAt: Date
updatedAt: Date
// Instance methods
toPublicJSON(): Partial<ITemplate>
isOwnedBy(userId: string): boolean
activate(): Promise<ITemplate>
deactivate(): Promise<ITemplate>
addTag(tag: string): Promise<ITemplate>
removeTag(tag: string): Promise<ITemplate>
}
// Mongoose schema
const templateSchema = new mongoose.Schema<ITemplate>(
{
name: {
type: String,
required: true,
trim: true,
maxlength: 100,
},
description: {
type: String,
trim: true,
maxlength: 500,
},
category: {
type: String,
enum: ['type1', 'type2', 'type3'],
default: 'type1',
},
tags: [
{
type: String,
trim: true,
lowercase: true,
},
],
isActive: {
type: Boolean,
default: true,
},
metadata: {
type: mongoose.Schema.Types.Mixed,
default: {},
},
// Relationships
userId: {
type: String,
required: true,
index: true,
},
parentId: {
type: String,
index: true,
},
// Nested objects
settings: {
visibility: {
type: String,
enum: ['public', 'private', 'unlisted'],
default: 'public',
},
allowComments: {
type: Boolean,
default: true,
},
featured: {
type: Boolean,
default: false,
},
},
// Arrays of objects
items: [
{
title: {
type: String,
required: true,
},
value: {
type: mongoose.Schema.Types.Mixed,
required: true,
},
order: {
type: Number,
default: 0,
},
},
],
},
{
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
)
// Indexes for performance
templateSchema.index({ userId: 1, isActive: 1 })
templateSchema.index({ category: 1, isActive: 1 })
templateSchema.index({ tags: 1 })
templateSchema.index({ createdAt: -1 })
templateSchema.index({ 'settings.featured': 1, isActive: 1 })
// Text search index
templateSchema.index({
name: 'text',
description: 'text',
tags: 'text',
})
// Virtual fields
templateSchema.virtual('itemCount').get(function () {
return this.items?.length || 0
})
templateSchema.virtual('url').get(function () {
return `/templates/${this._id}`
})
// Instance methods
templateSchema.methods.toPublicJSON = function (): Partial<ITemplate> {
const obj = this.toObject()
// Remove sensitive fields
delete obj.metadata
delete obj.__v
return obj
}
templateSchema.methods.isOwnedBy = function (userId: string): boolean {
return this.userId === userId
}
templateSchema.methods.activate = async function (): Promise<ITemplate> {
this.isActive = true
return this.save()
}
templateSchema.methods.deactivate = async function (): Promise<ITemplate> {
this.isActive = false
return this.save()
}
templateSchema.methods.addTag = async function (tag: string): Promise<ITemplate> {
const normalizedTag = tag.toLowerCase().trim()
if (!this.tags?.includes(normalizedTag)) {
if (!this.tags) this.tags = []
this.tags.push(normalizedTag)
return this.save()
}
return this
}
templateSchema.methods.removeTag = async function (tag: string): Promise<ITemplate> {
const normalizedTag = tag.toLowerCase().trim()
if (this.tags?.includes(normalizedTag)) {
this.tags = this.tags.filter((t) => t !== normalizedTag)
return this.save()
}
return this
}
// Static methods
templateSchema.statics.findByUser = function (userId: string) {
return this.find({ userId, isActive: true }).sort({ createdAt: -1 })
}
templateSchema.statics.findFeatured = function (limit: number = 10) {
return this.find({
'settings.featured': true,
isActive: true,
'settings.visibility': 'public',
})
.limit(limit)
.sort({ createdAt: -1 })
}
templateSchema.statics.findByCategory = function (category: string) {
return this.find({
category,
isActive: true,
'settings.visibility': 'public',
}).sort({ createdAt: -1 })
}
templateSchema.statics.search = function (query: string, limit: number = 20) {
return this.find(
{
$text: { $search: query },
isActive: true,
'settings.visibility': 'public',
},
{ score: { $meta: 'textScore' } }
)
.sort({ score: { $meta: 'textScore' } })
.limit(limit)
}
// Pre-save middleware
templateSchema.pre('save', function (next) {
// Normalize tags
if (this.tags) {
this.tags = this.tags.map((tag) => tag.toLowerCase().trim()).filter(Boolean)
// Remove duplicates
this.tags = [...new Set(this.tags)]
}
// Sort items by order
if (this.items && this.items.length > 0) {
this.items.sort((a, b) => a.order - b.order)
}
next()
})
// Post-save middleware
templateSchema.post('save', function (doc) {
console.log(`Template saved: ${doc.name} (${doc._id})`)
})
// Model interface with static methods
interface ITemplateModel extends Model<ITemplate> {
findByUser(userId: string): Promise<ITemplate[]>
findFeatured(limit?: number): Promise<ITemplate[]>
findByCategory(category: string): Promise<ITemplate[]>
search(query: string, limit?: number): Promise<ITemplate[]>
}
// Export the model
export const Template = (mongoose.models.Template ||
mongoose.model<ITemplate, ITemplateModel>('Template', templateSchema)) as ITemplateModel
/**
* Usage Examples:
*
* 1. Create a new template:
* const template = new Template({
* name: 'My Template',
* userId: 'user123',
* category: 'type1',
* });
* await template.save();
*
* 2. Find user's templates:
* const templates = await Template.findByUser('user123');
*
* 3. Search templates:
* const results = await Template.search('react component');
*
* 4. Validate data before saving:
* try {
* const validData = TemplateSchema.parse(inputData);
* const template = new Template(validData);
* await template.save();
* } catch (error) {
* // Handle validation error
* }
*/