343 lines
7.9 KiB
TypeScript
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
|
|
* }
|
|
*/
|