126 lines
3.0 KiB
TypeScript
126 lines
3.0 KiB
TypeScript
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)
|