initial commit

This commit is contained in:
Kar k1
2025-08-30 18:18:57 +05:30
commit 7219108342
270 changed files with 70221 additions and 0 deletions

311
models/billing.ts Normal file
View File

@@ -0,0 +1,311 @@
import mongoose, { Schema, Document } from 'mongoose'
import { z } from 'zod'
// Zod schema for validation
export const BillingSchema = z.object({
billing_id: z.string().min(1, 'Billing ID is required'),
service: z.string().min(1, 'Service name is required'),
service_type: z.enum([
'vps',
'kubernetes',
'developer_hire',
'vpn',
'hosting',
'storage',
'database',
'ai_service',
'custom',
]),
amount: z.number().min(0, 'Amount must be non-negative'),
currency: z.enum(['INR', 'USD']).default('INR'),
user_email: z.string().email('Invalid email format'),
silicon_id: z.string().min(1, 'Silicon ID is required'),
user_id: z.string().min(1, 'User ID is required'),
status: z.enum(['pending', 'active', 'completed', 'cancelled', 'failed', 'refunded']),
payment_status: z.enum(['pending', 'paid', 'failed', 'refunded']).default('pending'),
cycle: z.enum(['onetime', 'hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly']),
service_id: z.string().optional(),
cluster_id: z.string().optional(),
instance_id: z.string().optional(),
service_name: z.string().optional(),
service_config: z.record(z.string(), z.any()).optional(),
billing_period_start: z.date().optional(),
billing_period_end: z.date().optional(),
next_billing_date: z.date().optional(),
auto_renew: z.boolean().default(false),
discount_applied: z.number().min(0).default(0),
tax_amount: z.number().min(0).default(0),
total_amount: z.number().min(0),
transaction_id: z.string().optional(),
invoice_id: z.string().optional(),
remarks: z.string().optional(),
metadata: z.record(z.string(), z.any()).optional(),
service_status: z.number().min(0).max(2).default(0), // 0: inactive, 1: active, 2: suspended
created_by: z.string().optional(),
updated_by: z.string().optional(),
})
export type BillingType = z.infer<typeof BillingSchema>
// Mongoose interface
export interface IBilling extends Document {
billing_id: string
service: string
service_type:
| 'vps'
| 'kubernetes'
| 'developer_hire'
| 'vpn'
| 'hosting'
| 'storage'
| 'database'
| 'ai_service'
| 'custom'
amount: number
currency: 'INR' | 'USD'
user_email: string
silicon_id: string
user_id: string
status: 'pending' | 'active' | 'completed' | 'cancelled' | 'failed' | 'refunded'
payment_status: 'pending' | 'paid' | 'failed' | 'refunded'
cycle: 'onetime' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'yearly'
service_id?: string
cluster_id?: string
instance_id?: string
service_name?: string
service_config?: Record<string, any>
billing_period_start?: Date
billing_period_end?: Date
next_billing_date?: Date
auto_renew: boolean
discount_applied: number
tax_amount: number
total_amount: number
transaction_id?: string
invoice_id?: string
remarks?: string
metadata?: Record<string, any>
service_status: number
created_by?: string
updated_by?: string
createdAt: Date
updatedAt: Date
}
const billingSchema: Schema = new Schema<IBilling>(
{
billing_id: {
type: String,
required: true,
unique: true,
index: true,
},
service: {
type: String,
required: true,
trim: true,
},
service_type: {
type: String,
enum: [
'vps',
'kubernetes',
'developer_hire',
'vpn',
'hosting',
'storage',
'database',
'ai_service',
'custom',
],
required: true,
},
amount: {
type: Number,
required: true,
min: 0,
},
currency: {
type: String,
enum: ['INR', 'USD'],
default: 'INR',
},
user_email: {
type: String,
required: true,
lowercase: true,
trim: true,
index: true,
},
silicon_id: {
type: String,
required: true,
index: true,
},
user_id: {
type: String,
required: true,
index: true,
},
status: {
type: String,
enum: ['pending', 'active', 'completed', 'cancelled', 'failed', 'refunded'],
default: 'pending',
},
payment_status: {
type: String,
enum: ['pending', 'paid', 'failed', 'refunded'],
default: 'pending',
},
cycle: {
type: String,
enum: ['onetime', 'hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly'],
required: true,
},
service_id: {
type: String,
trim: true,
},
cluster_id: {
type: String,
trim: true,
},
instance_id: {
type: String,
trim: true,
},
service_name: {
type: String,
trim: true,
},
service_config: {
type: mongoose.Schema.Types.Mixed,
},
billing_period_start: {
type: Date,
},
billing_period_end: {
type: Date,
},
next_billing_date: {
type: Date,
},
auto_renew: {
type: Boolean,
default: false,
},
discount_applied: {
type: Number,
min: 0,
default: 0,
},
tax_amount: {
type: Number,
min: 0,
default: 0,
},
total_amount: {
type: Number,
required: true,
min: 0,
},
transaction_id: {
type: String,
trim: true,
},
invoice_id: {
type: String,
trim: true,
},
remarks: {
type: String,
trim: true,
},
metadata: {
type: mongoose.Schema.Types.Mixed,
},
service_status: {
type: Number,
min: 0,
max: 2,
default: 0,
},
created_by: {
type: String,
trim: true,
},
updated_by: {
type: String,
trim: true,
},
},
{
timestamps: true,
}
)
// Indexes for performance
billingSchema.index({ user_email: 1, createdAt: -1 })
billingSchema.index({ silicon_id: 1, createdAt: -1 })
billingSchema.index({ user_id: 1, createdAt: -1 })
billingSchema.index({ service_type: 1, status: 1 })
billingSchema.index({ payment_status: 1 })
billingSchema.index({ next_billing_date: 1, auto_renew: 1 })
billingSchema.index({ service_id: 1 })
billingSchema.index({ cluster_id: 1 })
billingSchema.index({ instance_id: 1 })
// Pre-save middleware to calculate total_amount
billingSchema.pre('save', function (next) {
if (
this.isModified('amount') ||
this.isModified('tax_amount') ||
this.isModified('discount_applied')
) {
const amount = Number(this.amount) || 0
const taxAmount = Number(this.tax_amount) || 0
const discountApplied = Number(this.discount_applied) || 0
this.total_amount = amount + taxAmount - discountApplied
}
next()
})
// Transform method to format response
billingSchema.methods.toJSON = function () {
const billing = this.toObject()
return billing
}
// Static methods for common queries
billingSchema.statics.findByUser = function (userEmail: string, siliconId: string) {
return this.find({
$or: [{ user_email: userEmail }, { silicon_id: siliconId }],
}).sort({ createdAt: -1 })
}
billingSchema.statics.findActiveServices = function (userEmail: string, siliconId: string) {
return this.find({
$or: [{ user_email: userEmail }, { silicon_id: siliconId }],
status: 'active',
service_status: 1,
}).sort({ createdAt: -1 })
}
billingSchema.statics.findByServiceType = function (
serviceType: string,
userEmail?: string,
siliconId?: string
) {
const query: any = { service_type: serviceType }
if (userEmail || siliconId) {
query.$or = []
if (userEmail) query.$or.push({ user_email: userEmail })
if (siliconId) query.$or.push({ silicon_id: siliconId })
}
return this.find(query).sort({ createdAt: -1 })
}
// Check if model already exists before compiling it
export const Billing =
(mongoose.models?.Billing as mongoose.Model<IBilling>) ||
mongoose.model<IBilling>('Billing', billingSchema)

114
models/developer-request.ts Normal file
View File

@@ -0,0 +1,114 @@
import mongoose, { Document, Schema } from 'mongoose'
export interface IDeveloperRequest extends Document {
userId: mongoose.Types.ObjectId
planId: string
planName: string
planPrice: number
requirements: string
contactInfo: {
name: string
email: string
phone?: string
}
status: 'pending' | 'approved' | 'in_progress' | 'completed' | 'cancelled'
paymentStatus: 'pending' | 'paid' | 'failed'
transactionId?: mongoose.Types.ObjectId
assignedDeveloper?: {
name: string
email: string
skills: string[]
}
estimatedStartDate?: Date
estimatedCompletionDate?: Date
actualStartDate?: Date
actualCompletionDate?: Date
notes?: string
createdAt: Date
updatedAt: Date
}
const DeveloperRequestSchema = new Schema<IDeveloperRequest>(
{
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
planId: {
type: String,
required: true,
enum: ['hourly', 'daily', 'monthly'],
},
planName: {
type: String,
required: true,
},
planPrice: {
type: Number,
required: true,
},
requirements: {
type: String,
required: true,
minlength: 10,
maxlength: 5000,
},
contactInfo: {
name: {
type: String,
required: true,
trim: true,
},
email: {
type: String,
required: true,
lowercase: true,
trim: true,
},
phone: {
type: String,
trim: true,
},
},
status: {
type: String,
enum: ['pending', 'approved', 'in_progress', 'completed', 'cancelled'],
default: 'pending',
},
paymentStatus: {
type: String,
enum: ['pending', 'paid', 'failed'],
default: 'pending',
},
transactionId: {
type: Schema.Types.ObjectId,
ref: 'Transaction',
},
assignedDeveloper: {
name: String,
email: String,
skills: [String],
},
estimatedStartDate: Date,
estimatedCompletionDate: Date,
actualStartDate: Date,
actualCompletionDate: Date,
notes: {
type: String,
maxlength: 2000,
},
},
{
timestamps: true,
}
)
// Indexes for better query performance
DeveloperRequestSchema.index({ userId: 1, createdAt: -1 })
DeveloperRequestSchema.index({ status: 1 })
DeveloperRequestSchema.index({ paymentStatus: 1 })
export const DeveloperRequest =
mongoose.models.DeveloperRequest ||
mongoose.model<IDeveloperRequest>('DeveloperRequest', DeveloperRequestSchema)

45
models/system-settings.ts Normal file
View File

@@ -0,0 +1,45 @@
import mongoose, { Schema, Document } from 'mongoose'
export interface ISystemSettings extends Document {
maintenanceMode: boolean
registrationEnabled: boolean
emailVerificationRequired: boolean
maxUserBalance: number
defaultUserRole: 'user' | 'admin'
systemMessage: string
paymentGatewayEnabled: boolean
developerHireEnabled: boolean
vpsDeploymentEnabled: boolean
kubernetesDeploymentEnabled: boolean
vpnServiceEnabled: boolean
lastUpdated: Date
updatedBy: string
}
const SystemSettingsSchema = new Schema<ISystemSettings>(
{
maintenanceMode: { type: Boolean, default: false },
registrationEnabled: { type: Boolean, default: true },
emailVerificationRequired: { type: Boolean, default: true },
maxUserBalance: { type: Number, default: 1000000 },
defaultUserRole: { type: String, enum: ['user', 'admin'], default: 'user' },
systemMessage: { type: String, default: '' },
paymentGatewayEnabled: { type: Boolean, default: true },
developerHireEnabled: { type: Boolean, default: true },
vpsDeploymentEnabled: { type: Boolean, default: true },
kubernetesDeploymentEnabled: { type: Boolean, default: true },
vpnServiceEnabled: { type: Boolean, default: true },
lastUpdated: { type: Date, default: Date.now },
updatedBy: { type: String, default: 'System' },
},
{
timestamps: true,
}
)
// Ensure only one settings document exists
SystemSettingsSchema.index({}, { unique: true })
export const SystemSettings =
mongoose.models.SystemSettings ||
mongoose.model<ISystemSettings>('SystemSettings', SystemSettingsSchema)

222
models/topic.ts Normal file
View File

@@ -0,0 +1,222 @@
import mongoose, { Schema, models, Document, Model } from 'mongoose'
import { z } from 'zod'
import readingTime from 'reading-time'
// Define tag schema
const tagSchema = z.object({
id: z.string().optional(),
name: z.string(),
})
// Zod schema for topic validation and transformation
export const topicSchema = z.object({
id: z.string(),
publishedAt: z.number(),
title: z.string(),
excerpt: z.string(),
coverImage: z.string(),
coverImageKey: z.string().optional(),
author: z.string(),
authorId: z.string(), // NEW: User ownership field
slug: z.string(),
content: z.string(),
contentRTE: z.unknown(),
contentImages: z.array(z.string()).optional(),
tags: z.array(tagSchema),
featured: z.boolean(),
isDraft: z.boolean().optional(),
views: z.number().optional().default(0), // NEW: View tracking
viewHistory: z.array(z.object({
date: z.string(),
count: z.number()
})).optional().default([]), // NEW: Daily analytics
readingTime: z.object({
text: z.string(),
minutes: z.number(),
time: z.number(),
words: z.number(),
}).optional(),
})
// Type inference from Zod schema
export type ITopic = z.infer<typeof topicSchema>
// Type for Mongoose document with ITopic
type TopicDocument = Document & ITopic
// Type for Mongoose model
type TopicModel = Model<ITopic>
// Generic utility function to transform and validate Mongoose document to any type
export const transformToType = <T>(
doc: Document | Record<string, unknown> | null,
schema: z.ZodType<T>
): T | null => {
if (!doc) return null
// Handle both Mongoose documents and plain objects
const data = 'toObject' in doc && typeof doc.toObject === 'function' ? doc.toObject() : doc
return schema.parse(data)
}
// Utility function to transform and validate Mongoose document to ITopic
export const transformToTopic = (doc: Document | Record<string, unknown> | null): ITopic | null => {
return transformToType(doc, topicSchema)
}
// Utility function to transform and validate multiple Mongoose documents to ITopic array
export const transformToTopics = (docs: (TopicDocument | Record<string, unknown>)[]): ITopic[] => {
return docs
.map((doc) => transformToType(doc, topicSchema))
.filter((topic): topic is ITopic => topic !== null)
}
const TopicSchema = new Schema<ITopic>(
{
id: {
type: String,
required: true,
unique: true,
},
publishedAt: {
type: Number,
required: true,
},
coverImage: {
type: String,
required: [true, 'Please provide a cover image for this topic'],
},
coverImageKey: {
type: String,
required: false,
},
title: {
type: String,
required: [true, 'Please provide a title for this topic'],
maxlength: [100, 'Title cannot be more than 100 characters'],
},
author: {
type: String,
required: [true, 'Please provide an author for this topic'],
},
authorId: {
type: String,
required: [true, 'Please provide an author ID for this topic'],
index: true, // NEW: Index for efficient queries by user
},
featured: {
type: Boolean,
default: false,
},
isDraft: {
type: Boolean,
default: false,
},
excerpt: {
type: String,
required: [true, 'Please provide a mini description for this topic'],
maxlength: [200, 'Mini description cannot be more than 200 characters'],
},
tags: {
type: [
{
id: { type: String },
name: { type: String, required: true },
},
],
required: [true, 'Please provide tags for this topic'],
},
slug: {
type: String,
unique: true,
sparse: true, // This allows the field to be unique only if it exists
},
content: {
type: String,
required: [true, 'Please provide content for this topic'],
},
contentRTE: {
type: Schema.Types.Mixed,
default: [],
},
contentImages: {
type: [String],
default: [],
},
readingTime: {
text: { type: String },
minutes: { type: Number },
time: { type: Number },
words: { type: Number },
},
views: {
type: Number,
default: 0,
index: true, // Index for efficient sorting by popularity
},
viewHistory: [{
date: { type: String, required: true }, // YYYY-MM-DD format
count: { type: Number, required: true, default: 1 },
_id: false, // Disable automatic _id generation for subdocuments
}],
},
{
timestamps: true,
minimize: false,
}
)
// Auto-generate slug from title if not provided
TopicSchema.pre('save', async function (next) {
if (!this.slug && this.title) {
// Create base slug from title
const baseSlug = this.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-') // Replace any sequence of non-alphanumeric chars with a single dash
.replace(/^-|-$/g, '') // Remove leading and trailing dashes
// First try using the base slug
let slug = baseSlug
// Check if an article with this slug already exists
const TopicModel = this.constructor as TopicModel
const existingTopic = await TopicModel.findOne({ slug: slug })
// If the slug already exists, append a timestamp to make it unique
if (existingTopic) {
// Generate a timestamp suffix (last 6 digits for brevity)
const timestamp = Date.now().toString().slice(-6)
slug = `${baseSlug}-${timestamp}`
}
this.slug = slug
}
// Auto-calculate reading time from content
if (this.content && this.isModified('content')) {
const stats = readingTime(this.content)
this.readingTime = {
text: stats.text,
minutes: Math.ceil(stats.minutes),
time: stats.time,
words: stats.words,
}
}
next()
})
// Index for efficient queries by user ownership
TopicSchema.index({ authorId: 1, publishedAt: -1 })
TopicSchema.index({ publishedAt: -1 }) // For public topic listing
TopicSchema.index({ slug: 1 }) // For individual topic lookup
TopicSchema.index({ 'tags.name': 1 }) // For tag-based filtering
TopicSchema.index({ isDraft: 1, authorId: 1 }) // For user's draft management
TopicSchema.index({ views: -1 }) // For sorting by popularity
TopicSchema.index({ authorId: 1, views: -1 }) // For user's most viewed posts
// Check if the model exists before creating a new one
// This prevents "Cannot overwrite model once compiled" errors
const TopicModel = (models.topics as TopicModel) || mongoose.model<ITopic>('topics', TopicSchema)
export default TopicModel

121
models/transaction.ts Normal file
View File

@@ -0,0 +1,121 @@
import mongoose, { Document, Model } from 'mongoose'
import { z } from 'zod'
// Zod schema for validation
export const TransactionSchema = z.object({
transactionId: z.string().min(1, 'Transaction ID is required'),
userId: z.string().min(1, 'User ID is required'),
email: z.string().email('Invalid email format'),
type: z.enum(['debit', 'credit']),
amount: z.number().positive('Amount must be positive'),
service: z.string().min(1, 'Service name is required'),
serviceId: z.string().optional(),
description: z.string().optional(),
status: z.enum(['pending', 'completed', 'failed']).default('completed'),
previousBalance: z.number().min(0, 'Previous balance cannot be negative'),
newBalance: z.number().min(0, 'New balance cannot be negative'),
metadata: z.record(z.string(), z.any()).optional(),
})
export type TransactionType = z.infer<typeof TransactionSchema>
// Mongoose interface
export interface ITransaction extends Document {
transactionId: string
userId: string
email: string
type: 'debit' | 'credit'
amount: number
service: string
serviceId?: string
description?: string
status: 'pending' | 'completed' | 'failed'
previousBalance: number
newBalance: number
metadata?: Record<string, any>
createdAt: Date
updatedAt: Date
}
// Mongoose schema
const transactionSchema = new mongoose.Schema<ITransaction>(
{
transactionId: {
type: String,
required: true,
unique: true,
index: true,
},
userId: {
type: String,
required: true,
index: true,
},
email: {
type: String,
required: true,
lowercase: true,
trim: true,
index: true,
},
type: {
type: String,
enum: ['debit', 'credit'],
required: true,
},
amount: {
type: Number,
required: true,
min: 0,
},
service: {
type: String,
required: true,
trim: true,
},
serviceId: {
type: String,
trim: true,
},
description: {
type: String,
trim: true,
},
status: {
type: String,
enum: ['pending', 'completed', 'failed'],
default: 'completed',
},
previousBalance: {
type: Number,
required: true,
min: 0,
},
newBalance: {
type: Number,
required: true,
min: 0,
},
metadata: {
type: mongoose.Schema.Types.Mixed,
},
},
{
timestamps: true,
}
)
// Indexes for performance
transactionSchema.index({ userId: 1, createdAt: -1 })
transactionSchema.index({ email: 1, createdAt: -1 })
transactionSchema.index({ service: 1, createdAt: -1 })
transactionSchema.index({ type: 1, createdAt: -1 })
// Transform method to format response
transactionSchema.methods.toJSON = function () {
const transaction = this.toObject()
return transaction
}
export const Transaction: Model<ITransaction> =
mongoose.models.Transaction || mongoose.model<ITransaction>('Transaction', transactionSchema)

125
models/user.ts Normal file
View File

@@ -0,0 +1,125 @@
import mongoose, { Document, Model } from 'mongoose'
import { z } from 'zod'
import bcrypt from 'bcryptjs'
// Zod schema for validation
export const UserSchema = z.object({
email: z.string().email('Invalid email format'),
name: z.string().min(1, 'Name is required'),
password: z.string().min(6, 'Password must be at least 6 characters'),
siliconId: z.string().optional(),
role: z.enum(['user', 'admin']).optional().default('user'),
isVerified: z.boolean().optional().default(false),
provider: z.string().optional(),
providerId: z.string().optional(),
avatar: z.string().url().optional(),
})
export type UserType = z.infer<typeof UserSchema>
// Mongoose interface
export interface IUser extends Document {
email: string
name: string
password?: string
siliconId: string
role: 'user' | 'admin'
isVerified: boolean
provider: 'local' | 'google' | 'github'
providerId?: string
avatar?: string
refreshToken?: string
balance?: number
lastLogin: Date
createdAt: Date
updatedAt: Date
comparePassword(candidatePassword: string): Promise<boolean>
}
// Mongoose schema
const userSchema = new mongoose.Schema<IUser>(
{
email: {
type: String,
required: true,
lowercase: true,
trim: true,
},
name: {
type: String,
required: true,
trim: true,
},
siliconId: {
type: String,
required: true,
trim: true,
},
password: {
type: String,
required: function (this: any) {
return !this.provider // Password required only if not using OAuth
},
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user',
},
balance: {
type: Number,
default: 0,
min: 0,
},
isVerified: {
type: Boolean,
default: false,
},
provider: {
type: String,
enum: ['local', 'google', 'github'],
default: 'local',
},
providerId: String,
avatar: String,
refreshToken: String,
lastLogin: {
type: Date,
default: Date.now,
},
},
{
timestamps: true,
}
)
// Index for performance
userSchema.index({ email: 1 }, { unique: true })
userSchema.index({ siliconId: 1 }, { unique: true })
userSchema.index({ providerId: 1, provider: 1 })
// Hash password before saving
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next()
if (this.password) {
this.password = await bcrypt.hash(this.password, 12)
}
next()
})
// Compare password method
userSchema.methods.comparePassword = async function (candidatePassword: string): Promise<boolean> {
if (!this.password) return false
return bcrypt.compare(candidatePassword, this.password)
}
// Transform method to remove sensitive fields
userSchema.methods.toJSON = function () {
const user = this.toObject()
delete user.password
delete user.refreshToken
return user
}
export const User: Model<IUser> = mongoose.models.User || mongoose.model<IUser>('User', userSchema)