@sehirapp/core-microservice
Version:
Modern mikroservis core paketi - MongoDB 6.7, Express API, Mongoose, PM2 cluster desteği
1,157 lines (999 loc) • 38.9 kB
JavaScript
import { mongoose } from "./database.js";
import dbManager from "./database.js";
import * as f from "../utils/functions.js";
import { logger } from "./logger.js";
/**
* Modern CoreClass - Mongoose şemaları ile entegrasyon
* Mikroservislerde temel model sınıfı
*/
export default class CoreClass {
constructor() {
this.dbManager = dbManager;
this._mongooseModel = null;
this._useMongoose = process.env.USE_MONGOOSE !== 'false';
this._schema = null;
this.connectionType = this._useMongoose ? 'mongoose' : 'mongodb';
// Default timestamp'ler - epoch milisaniye
const now = Date.now();
if (!this.createdAt) this.createdAt = now;
if (!this.updatedAt) this.updatedAt = now;
}
/**
* Mongoose şeması tanımla - alt sınıflarda override edilecek
*/
Schema() {
throw new Error('Schema() method must be implemented in subclass');
}
/**
* Collection adı - alt sınıflarda override edilecek
*/
Collection() {
throw new Error('Collection() method must be implemented in subclass');
}
/**
* Unique alanlar - alt sınıflarda override edilecek
*/
Uniques() {
return ['id'];
}
/**
* Mongoose model'ini al veya oluştur
*/
getMongooseModel() {
if (!this._mongooseModel) {
const schema = this.Schema();
if (!schema) {
throw new Error('Schema is required for Mongoose model');
}
// Şemaya otomatik alanlar ekle - Epoch milisaniye timestamp olarak
if (!schema.paths.createdAt) {
schema.add({
createdAt: {
type: Number,
default: Date.now,
index: true
}
});
}
if (!schema.paths.updatedAt) {
schema.add({
updatedAt: {
type: Number,
default: Date.now,
index: true
}
});
}
const modelName = this.constructor.name;
// Model zaten var mı kontrol et
if (mongoose.models[modelName]) {
this._mongooseModel = mongoose.models[modelName];
} else {
this._mongooseModel = mongoose.model(modelName, schema, this.Collection());
}
}
return this._mongooseModel;
}
/**
* Veri parametrelerini ayarla
*/
setParameters(data, schema = null) {
if (!data) return;
const schemaDefinition = schema || this.Schema();
if (!schemaDefinition) return;
const paths = schemaDefinition.paths || {};
Object.keys(data).forEach(key => {
if (data[key] !== undefined && data[key] !== null) {
const schemaPath = paths[key];
// Date alanlarını epoch milisaniye'ye çevir (Number tip)
if (schemaPath && schemaPath.instance === 'Number' &&
(key === 'createdAt' || key === 'updatedAt' || key === 'deletedAt' || key.includes('At'))) {
if (data[key] instanceof Date) {
this[key] = data[key].getTime();
} else if (typeof data[key] === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(data[key])) {
this[key] = new Date(data[key]).getTime();
} else if (typeof data[key] === 'number') {
this[key] = data[key]; // Zaten epoch format
} else {
this[key] = data[key];
}
// Formatlanmış tarih alanları ekle
this[key + "_F"] = f.dateFormat(this[key]);
this[key + "_T"] = f.timeFormat(this[key]);
this[key + "_FT"] = f.dateFormat(this[key]) + " " + f.timeFormat(this[key]);
} else {
// Normal alanlar
this[key] = data[key];
// Legacy Date formatlaması (eski Date tip alanları için)
if (schemaPath && schemaPath.instance === 'Date') {
this[key + "_F"] = f.dateFormat(data[key]);
this[key + "_T"] = f.timeFormat(data[key]);
this[key + "_FT"] = f.dateFormat(data[key]) + " " + f.timeFormat(data[key]);
}
// Para formatlaması
if (schemaPath && schemaPath.options && schemaPath.options.type === 'money') {
this[key + "_F"] = f.monetize(data[key]);
}
}
}
});
}
/**
* Database bağlantısını sağla
*/
async ensureConnection() {
if (this._useMongoose) {
await this.dbManager.connectMongoose();
} else {
await this.dbManager.connect();
}
}
/**
* Kayıt ara - Mongoose ve MongoDB native destekli
*/
async find(findBy = null) {
const startTime = Date.now();
const filter = findBy || { id: this.id };
try {
logger.debug(`Finding ${this.constructor.name}`, { filter });
await this.ensureConnection();
let result = null;
if (this._useMongoose) {
const Model = this.getMongooseModel();
result = await Model.findOne(filter).lean();
} else {
const results = await this.dbManager.find(this.Collection(), filter, { limit: 1 });
result = results[0] || null;
}
const duration = Date.now() - startTime;
if (result) {
// Sonucu bu objeye ata
Object.keys(result).forEach(key => {
this[key] = result[key];
});
logger.info(`${this.constructor.name} found`, {
id: this.id,
duration: `${duration}ms`
});
return true;
} else {
logger.warn(`${this.constructor.name} not found`, {
filter,
duration: `${duration}ms`
});
return false;
}
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Error finding ${this.constructor.name}`, {
error: error.message,
filter,
duration: `${duration}ms`
});
throw error;
}
}
/**
* Kayıt var mı kontrol et - unique alanlar kontrollü
*/
async ifExists() {
const startTime = Date.now();
try {
const uniques = this.Uniques();
const orConditions = [{ id: this.id }];
// Unique alanları kontrol et
uniques.forEach(key => {
if (this[key] !== undefined && this[key] !== null && this[key] !== '') {
const condition = {};
condition[key] = this[key];
orConditions.push(condition);
}
});
const filter = { $or: orConditions };
await this.ensureConnection();
let result = null;
if (this._useMongoose) {
const Model = this.getMongooseModel();
result = await Model.findOne(filter).lean();
} else {
const results = await this.dbManager.find(this.Collection(), filter, { limit: 1 });
result = results[0] || null;
}
const duration = Date.now() - startTime;
if (result) {
// Hangi unique alan çakışıyor bul
for (const key of uniques) {
if (result[key] === this[key]) {
logger.info(`${this.constructor.name} exists - unique conflict`, {
conflictKey: key,
duration: `${duration}ms`
});
return key;
}
}
logger.info(`${this.constructor.name} exists`, {
id: result.id,
duration: `${duration}ms`
});
return true;
} else {
logger.debug(`${this.constructor.name} does not exist`, {
duration: `${duration}ms`
});
return false;
}
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Error checking existence of ${this.constructor.name}`, {
error: error.message,
duration: `${duration}ms`
});
throw error;
}
}
/**
* Yeni kayıt ekle
*/
async insert() {
const startTime = Date.now();
try {
logger.debug(`Inserting ${this.constructor.name}`, { id: this.id });
const exists = await this.ifExists();
if (exists === false) {
// Temiz veri hazırla
const cleanData = this.getCleanData();
await this.ensureConnection();
if (this._useMongoose) {
const Model = this.getMongooseModel();
const doc = new Model(cleanData);
await doc.save();
// Kaydedilen veriyi geri al
Object.keys(doc.toObject()).forEach(key => {
this[key] = doc[key];
});
} else {
await this.dbManager.insert(this.Collection(), cleanData);
}
const duration = Date.now() - startTime;
logger.info(`${this.constructor.name} inserted`, {
id: this.id,
duration: `${duration}ms`
});
return true;
} else if (exists === true) {
const duration = Date.now() - startTime;
logger.warn(`${this.constructor.name} insert failed - already exists`, {
id: this.id,
duration: `${duration}ms`
});
return "Bu kayıt zaten mevcut!";
} else {
const duration = Date.now() - startTime;
logger.warn(`${this.constructor.name} insert failed - unique constraint`, {
id: this.id,
conflictKey: exists,
duration: `${duration}ms`
});
return `Bu kayıt zaten mevcut! (${exists})`;
}
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Error inserting ${this.constructor.name}`, {
error: error.message,
id: this.id,
duration: `${duration}ms`
});
throw error;
}
}
/**
* Kayıt güncelle
*/
async update() {
const startTime = Date.now();
try {
logger.debug(`Updating ${this.constructor.name}`, { id: this.id });
const cleanData = this.getCleanData();
cleanData.updatedAt = Date.now(); // Epoch milisaniye timestamp
await this.ensureConnection();
if (this._useMongoose) {
const Model = this.getMongooseModel();
const result = await Model.updateOne({ id: this.id }, cleanData);
const duration = Date.now() - startTime;
if (result.modifiedCount > 0) {
logger.info(`${this.constructor.name} updated`, {
id: this.id,
duration: `${duration}ms`
});
} else {
logger.warn(`${this.constructor.name} update - no changes`, {
id: this.id,
duration: `${duration}ms`
});
}
} else {
await this.dbManager.update(this.Collection(), { id: this.id }, cleanData);
const duration = Date.now() - startTime;
logger.info(`${this.constructor.name} updated`, {
id: this.id,
duration: `${duration}ms`
});
}
return true;
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Error updating ${this.constructor.name}`, {
error: error.message,
id: this.id,
duration: `${duration}ms`
});
throw error;
}
}
/**
* Kaydet (insert veya update)
*/
async save() {
const startTime = Date.now();
try {
logger.debug(`Saving ${this.constructor.name}`, { id: this.id });
const exists = await this.find();
let result;
if (exists) {
result = await this.update();
logger.info(`${this.constructor.name} saved via update`, {
id: this.id,
duration: `${Date.now() - startTime}ms`
});
} else {
result = await this.insert();
logger.info(`${this.constructor.name} saved via insert`, {
id: this.id,
duration: `${Date.now() - startTime}ms`
});
}
return result;
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Error saving ${this.constructor.name}`, {
error: error.message,
id: this.id,
duration: `${duration}ms`
});
return false;
}
}
/**
* Kayıt sil
*/
async delete() {
const startTime = Date.now();
try {
logger.debug(`Deleting ${this.constructor.name}`, { id: this.id });
await this.ensureConnection();
if (this._useMongoose) {
const Model = this.getMongooseModel();
const result = await Model.deleteOne({ id: this.id });
const duration = Date.now() - startTime;
if (result.deletedCount > 0) {
logger.info(`${this.constructor.name} deleted`, {
id: this.id,
duration: `${duration}ms`
});
return true;
} else {
logger.warn(`${this.constructor.name} delete - not found`, {
id: this.id,
duration: `${duration}ms`
});
return false;
}
} else {
const result = await this.dbManager.remove(this.Collection(), { id: this.id });
const duration = Date.now() - startTime;
logger.info(`${this.constructor.name} deleted`, {
id: this.id,
duration: `${duration}ms`
});
return result;
}
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Error deleting ${this.constructor.name}`, {
error: error.message,
id: this.id,
duration: `${duration}ms`
});
throw error;
}
}
/**
* Tüm kayıtları getir
*/
async GetAll(filter = {}, options = {}) {
const startTime = Date.now();
try {
const {
sort = null,
limit = 1000000,
skip = 0,
project = null,
populate = null
} = options;
logger.debug(`Getting all ${this.constructor.name} records`, {
filter,
sort,
limit,
skip,
project
});
await this.ensureConnection();
let results = [];
if (this._useMongoose) {
const Model = this.getMongooseModel();
let query = Model.find(filter);
if (sort) query = query.sort(sort);
if (skip > 0) query = query.skip(skip);
if (limit < 1000000) query = query.limit(limit);
if (project) query = query.select(project);
if (populate) query = query.populate(populate);
results = await query.lean();
} else {
results = await this.dbManager.find(this.Collection(), filter, {
sort,
limit,
skip,
project
});
}
const duration = Date.now() - startTime;
logger.info(`Retrieved ${results.length} ${this.constructor.name} records`, {
count: results.length,
duration: `${duration}ms`
});
return results;
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Error getting all ${this.constructor.name} records`, {
error: error.message,
filter,
duration: `${duration}ms`
});
return [];
}
}
/**
* Tek kayıt getir
*/
async findOne(filter = {}) {
const startTime = Date.now();
try {
await this.ensureConnection();
let result = null;
if (this._useMongoose) {
const Model = this.getMongooseModel();
result = await Model.findOne(filter).lean();
} else {
const results = await this.dbManager.find(this.Collection(), filter, { limit: 1 });
result = results[0] || null;
}
const duration = Date.now() - startTime;
if (result) {
logger.debug(`${this.constructor.name} findOne success`, {
filter,
duration: `${duration}ms`
});
return result;
} else {
logger.debug(`${this.constructor.name} findOne - not found`, {
filter,
duration: `${duration}ms`
});
return null;
}
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Error in ${this.constructor.name} findOne`, {
error: error.message,
filter,
duration: `${duration}ms`
});
throw error;
}
}
/**
* Kayıt sayısı
*/
async count(filter = {}) {
const startTime = Date.now();
try {
await this.ensureConnection();
let count = 0;
if (this._useMongoose) {
const Model = this.getMongooseModel();
count = await Model.countDocuments(filter);
} else {
count = await this.dbManager.count(this.Collection(), filter);
}
const duration = Date.now() - startTime;
logger.debug(`${this.constructor.name} count: ${count}`, {
filter,
duration: `${duration}ms`
});
return count;
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Error counting ${this.constructor.name}`, {
error: error.message,
filter,
duration: `${duration}ms`
});
return 0;
}
}
/**
* Temiz veri hazırla - formatlanmış alanları çıkar
*/
getCleanData() {
const cleanData = {};
Object.keys(this).forEach(key => {
// Formatlanmış alanları ve private alanları çıkar
if (!key.includes("_F") &&
!key.includes("_T") &&
!key.includes("_FT") &&
!key.startsWith("_") &&
key !== 'dbManager' &&
key !== 'connectionType' && // Database connection type'ı çıkar
typeof this[key] !== 'function'
) {
cleanData[key] = this[key];
}
});
return cleanData;
}
/**
* JSON çıktısı - Database'de zaten epoch milisaniye formatında
*/
toJSON() {
const data = this.getCleanData();
// Date alanları zaten epoch formatında (Number tip)
// Legacy support için ISO string'leri epoch'a çevir
Object.keys(data).forEach(key => {
const value = data[key];
// Legacy Date object'leri epoch'a çevir
if (value instanceof Date) {
data[key] = value.getTime();
}
// Legacy ISO string'leri epoch'a çevir
else if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
data[key] = new Date(value).getTime();
}
// Nested object'lerde de kontrol et
if (value && typeof value === 'object' && !Array.isArray(value)) {
Object.keys(value).forEach(nestedKey => {
const nestedValue = value[nestedKey];
if (nestedValue instanceof Date) {
value[nestedKey] = nestedValue.getTime();
} else if (typeof nestedValue === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(nestedValue)) {
value[nestedKey] = new Date(nestedValue).getTime();
}
});
}
});
return data;
}
/**
* Toplu işlemler framework'ü - çoklu kayıt işlemleri için
* @param {Array} items - İşlenecek kayıtlar
* @param {string} operation - İşlem tipi ('create', 'update', 'delete')
* @param {Object} options - İşlem seçenekleri
* @returns {Promise<Object>} İşlem sonuçları
*/
async executeBulkOperation(items, operation, options = {}) {
const startTime = Date.now();
const {
continueOnError = false,
validateBefore = true,
batchSize = 100,
parallel = false
} = options;
const results = {
success: [],
failed: [],
total: items.length,
operation,
duration: 0,
successCount: 0,
failedCount: 0
};
if (!Array.isArray(items) || items.length === 0) {
logger.warn('Bulk operation called with invalid items', {
itemType: typeof items,
itemLength: items?.length,
operation
});
return results;
}
try {
logger.info(`Starting bulk ${operation} operation`, {
totalItems: items.length,
batchSize,
parallel,
model: this.constructor.name
});
// Batch işlem fonksiyonu
const processBatch = async (batch, batchIndex) => {
const batchResults = { success: [], failed: [] };
if (parallel) {
// Paralel işlem
const promises = batch.map(async (item, itemIndex) => {
const globalIndex = batchIndex * batchSize + itemIndex;
return this.processItem(item, operation, validateBefore, globalIndex);
});
const batchResponses = await Promise.allSettled(promises);
batchResponses.forEach((response, itemIndex) => {
const globalIndex = batchIndex * batchSize + itemIndex;
if (response.status === 'fulfilled') {
batchResults.success.push({
index: globalIndex,
item: batch[itemIndex],
result: response.value
});
} else {
batchResults.failed.push({
index: globalIndex,
item: batch[itemIndex],
error: response.reason?.message || 'Unknown error'
});
}
});
} else {
// Seri işlem
for (let itemIndex = 0; itemIndex < batch.length; itemIndex++) {
const globalIndex = batchIndex * batchSize + itemIndex;
const item = batch[itemIndex];
try {
const result = await this.processItem(item, operation, validateBefore, globalIndex);
batchResults.success.push({
index: globalIndex,
item,
result
});
} catch (error) {
batchResults.failed.push({
index: globalIndex,
item,
error: error.message
});
if (!continueOnError) {
logger.warn('Bulk operation stopped due to error', {
error: error.message,
itemIndex: globalIndex,
operation
});
break;
}
}
}
}
return batchResults;
};
// Batch'lere ayır ve işle
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchIndex = Math.floor(i / batchSize);
logger.debug(`Processing batch ${batchIndex + 1}`, {
batchSize: batch.length,
startIndex: i,
operation
});
const batchResults = await processBatch(batch, batchIndex);
results.success.push(...batchResults.success);
results.failed.push(...batchResults.failed);
if (!continueOnError && batchResults.failed.length > 0) {
break;
}
}
results.successCount = results.success.length;
results.failedCount = results.failed.length;
results.duration = Date.now() - startTime;
logger.info(`Bulk ${operation} operation completed`, {
total: results.total,
success: results.successCount,
failed: results.failedCount,
duration: `${results.duration}ms`,
model: this.constructor.name
});
return results;
} catch (error) {
results.duration = Date.now() - startTime;
logger.error(`Bulk ${operation} operation failed`, {
error: error.message,
processed: results.success.length + results.failed.length,
total: results.total,
duration: `${results.duration}ms`,
stack: error.stack
});
throw error;
}
}
/**
* Tek kayıt işlemi - bulk operation helper
*/
async processItem(item, operation, validateBefore, index) {
const modelInstance = new this.constructor(item);
if (validateBefore) {
const validation = modelInstance.validate();
if (!validation.isValid) {
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
}
}
let result;
switch (operation.toLowerCase()) {
case 'create':
case 'insert':
result = await modelInstance.insert();
break;
case 'update':
if (item.id) {
const found = await modelInstance.find({ id: item.id });
if (!found) {
throw new Error(`Record not found for update: ${item.id}`);
}
modelInstance.setParameters(item);
result = await modelInstance.update();
} else {
throw new Error('ID is required for update operation');
}
break;
case 'delete':
if (item.id) {
const found = await modelInstance.find({ id: item.id });
if (!found) {
throw new Error(`Record not found for delete: ${item.id}`);
}
result = await modelInstance.delete();
} else {
throw new Error('ID is required for delete operation');
}
break;
case 'save':
case 'upsert':
result = await modelInstance.save();
break;
default:
throw new Error(`Unsupported bulk operation: ${operation}`);
}
return result;
}
/**
* Metadata yönetimi - model'e metadata ekleme/güncelleme
* @param {string} field - Metadata alanı
* @param {any} value - Değer
*/
updateMetadata(field, value) {
if (!field || typeof field !== 'string') {
throw new Error('Metadata field must be a non-empty string');
}
// Metadata objesi yoksa oluştur
if (!this.metadata || typeof this.metadata !== 'object') {
this.metadata = {};
}
// Eski değeri kaydet (history için)
const oldValue = this.metadata[field];
// Yeni değeri ata
this.metadata[field] = value;
this.metadata.lastUpdatedAt = Date.now();
// Metadata history oluştur
if (!this.metadata._history) {
this.metadata._history = [];
}
this.metadata._history.push({
field,
oldValue,
newValue: value,
updatedAt: Date.now(),
updatedBy: this.metadata.updatedBy || 'system'
});
// History'yi maksimum 50 kayıt ile sınırla
if (this.metadata._history.length > 50) {
this.metadata._history = this.metadata._history.slice(-50);
}
// updatedAt'i güncelle
this.updatedAt = Date.now();
logger.debug(`Metadata updated`, {
model: this.constructor.name,
id: this.id,
field,
oldValue: typeof oldValue,
newValue: typeof value
});
return this;
}
/**
* Metadata silme
* @param {string} field - Silinecek metadata alanı
*/
removeMetadata(field) {
if (!this.metadata || !field) return this;
const oldValue = this.metadata[field];
delete this.metadata[field];
// History'ye ekle
this.updateMetadata('_removed', {
field,
value: oldValue,
removedAt: Date.now()
});
logger.debug(`Metadata removed`, {
model: this.constructor.name,
id: this.id,
field,
oldValue: typeof oldValue
});
return this;
}
/**
* Metadata sorgulama
* @param {string} field - Sorgulanacak alan
* @returns {any} Metadata değeri
*/
getMetadata(field) {
if (!this.metadata) return undefined;
return this.metadata[field];
}
/**
* Status yönetimi - doğrulama ile
* @param {string} newStatus - Yeni status
* @param {Array<string>} validStatuses - Geçerli status'lar
* @param {Object} options - Seçenekler
*/
updateStatus(newStatus, validStatuses = [], options = {}) {
const {
reason = null,
updatedBy = 'system',
allowSameStatus = false,
validateTransition = true
} = options;
if (!newStatus || typeof newStatus !== 'string') {
throw new Error('Status must be a non-empty string');
}
// Geçerli status kontrolü
if (validStatuses.length > 0 && !validStatuses.includes(newStatus)) {
throw new Error(`Invalid status "${newStatus}". Valid statuses: ${validStatuses.join(', ')}`);
}
const oldStatus = this.status;
// Aynı status kontrolü
if (!allowSameStatus && oldStatus === newStatus) {
logger.debug(`Status update skipped - same status`, {
model: this.constructor.name,
id: this.id,
status: newStatus
});
return this;
}
// Status geçiş validasyonu (alt sınıflarda override edilebilir)
if (validateTransition && this.validateStatusTransition) {
const isValidTransition = this.validateStatusTransition(oldStatus, newStatus);
if (!isValidTransition) {
throw new Error(`Invalid status transition from "${oldStatus}" to "${newStatus}"`);
}
}
// Status güncelle
this.status = newStatus;
this.updatedAt = Date.now();
// Status history oluştur
if (!this.statusHistory) {
this.statusHistory = [];
}
this.statusHistory.push({
from: oldStatus,
to: newStatus,
updatedAt: Date.now(),
updatedBy,
reason
});
// History'yi maksimum 100 kayıt ile sınırla
if (this.statusHistory.length > 100) {
this.statusHistory = this.statusHistory.slice(-100);
}
// Metadata'ya da kaydet
this.updateMetadata('lastStatusChange', {
from: oldStatus,
to: newStatus,
updatedAt: Date.now(),
updatedBy,
reason
});
logger.info(`Status updated`, {
model: this.constructor.name,
id: this.id,
from: oldStatus,
to: newStatus,
reason,
updatedBy
});
return this;
}
/**
* Status geçiş validasyonu - alt sınıflarda override edilebilir
* @param {string} fromStatus - Mevcut status
* @param {string} toStatus - Hedef status
* @returns {boolean} Geçiş geçerli mi
*/
validateStatusTransition(fromStatus, toStatus) {
// Varsayılan: tüm geçişlere izin ver
// Alt sınıflarda specific business logic uygulanabilir
return true;
}
/**
* Model instance'ının son aktivite zamanını güncelle
*/
touch() {
this.updatedAt = Date.now();
this.updateMetadata('lastTouch', Date.now());
return this;
}
/**
* Model soft delete
* @param {Object} options - Silme seçenekleri
*/
async softDelete(options = {}) {
const { deletedBy = 'system', reason = null } = options;
this.deletedAt = Date.now();
this.isDeleted = true;
// Metadata güncelle
this.updateMetadata('deletion', {
deletedAt: Date.now(),
deletedBy,
reason
});
// Status güncelle
if (this.status && this.status !== 'deleted') {
this.updateStatus('deleted', [], {
reason: `Soft deleted: ${reason || 'No reason provided'}`,
updatedBy: deletedBy
});
}
const result = await this.update();
logger.info(`Model soft deleted`, {
model: this.constructor.name,
id: this.id,
deletedBy,
reason
});
return result;
}
/**
* Soft delete'i geri al
* @param {Object} options - Restore seçenekleri
*/
async restore(options = {}) {
const { restoredBy = 'system', newStatus = 'active' } = options;
this.deletedAt = null;
this.isDeleted = false;
// Metadata güncelle
this.updateMetadata('restoration', {
restoredAt: Date.now(),
restoredBy
});
// Status güncelle
this.updateStatus(newStatus, [], {
reason: 'Restored from soft delete',
updatedBy: restoredBy
});
const result = await this.update();
logger.info(`Model restored`, {
model: this.constructor.name,
id: this.id,
restoredBy,
newStatus
});
return result;
}
/**
* Validation - alt sınıflarda override edilebilir
*/
validate() {
return { isValid: true, errors: [] };
}
}