initial commit
This commit is contained in:
342
templates/model.template.ts
Normal file
342
templates/model.template.ts
Normal file
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* 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
|
||||
* }
|
||||
*/
|
||||
Reference in New Issue
Block a user