UNPKG

@bitwild/rockets-auth

Version:

Rockets Auth - Complete authentication and authorization solution for NestJS with JWT, OAuth, OTP, role-based access control, and more

611 lines 22.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SqliteRepositoryAdapter = void 0; exports.Column = Column; exports.Table = Table; const sqlite3 = __importStar(require("sqlite3")); const nestjs_common_1 = require("@concepta/nestjs-common"); const COLUMN_METADATA_KEY = Symbol('column_metadata'); const TABLE_METADATA_KEY = Symbol('table_metadata'); function Column(options = {}) { return function (target, propertyKey) { const existingMetadata = Reflect.getMetadata(COLUMN_METADATA_KEY, target) || {}; existingMetadata[propertyKey] = options; Reflect.defineMetadata(COLUMN_METADATA_KEY, existingMetadata, target); }; } function Table(options = {}) { return function (target) { Reflect.defineMetadata(TABLE_METADATA_KEY, options, target); }; } class SqliteRepositoryAdapter { constructor(entityClass, dbPath = './rockets.sqlite') { this.columnCache = null; this.entityClass = entityClass; this.tableName = this.getTableNameFromEntity(entityClass); this.db = new sqlite3.Database(dbPath); this.initializeTable(); } getTableNameFromEntity(entityClass) { try { if (typeof Reflect !== 'undefined' && Reflect.getMetadata) { const tableMetadata = Reflect.getMetadata(TABLE_METADATA_KEY, entityClass) || {}; if (tableMetadata.name) { return tableMetadata.name; } } } catch (e) { } const className = entityClass.name; return (className .replace(/([A-Z])/g, '_$1') .toLowerCase() .substring(1) + 's'); } analyzeEntityProperties() { var _a; if (this.columnCache) { return this.columnCache; } const properties = {}; const propertyNames = new Set(); try { const globalWithTypeORM = global; const typeormMetadataStorage = globalWithTypeORM.typeormMetadataStorage; const metadata = (_a = typeormMetadataStorage === null || typeormMetadataStorage === void 0 ? void 0 : typeormMetadataStorage.tables) === null || _a === void 0 ? void 0 : _a.find((table) => table.target === this.entityClass); if (metadata === null || metadata === void 0 ? void 0 : metadata.columns) { metadata.columns.forEach((column) => { propertyNames.add(column.propertyName); }); } } catch (error) { } let currentPrototype = this.entityClass.prototype; while (currentPrototype && currentPrototype !== Object.prototype) { Object.getOwnPropertyNames(currentPrototype).forEach((name) => { if (name !== 'constructor' && !name.startsWith('_')) { const descriptor = Object.getOwnPropertyDescriptor(currentPrototype, name); if (!descriptor || typeof descriptor.value !== 'function') { propertyNames.add(name); } } }); currentPrototype = Object.getPrototypeOf(currentPrototype); } try { const sampleEntity = new this.entityClass(); Object.getOwnPropertyNames(sampleEntity).forEach((name) => { if (!name.startsWith('_')) { propertyNames.add(name); } }); } catch (error) { } if (propertyNames.size === 0) { ['id', 'dateCreated', 'dateUpdated', 'dateDeleted', 'version'].forEach((prop) => { propertyNames.add(prop); }); } propertyNames.add('id'); propertyNames.forEach((propertyName) => { let metadata = null; try { const sampleEntity = new this.entityClass(); metadata = this.inferColumnMetadata(sampleEntity, propertyName); } catch (error) { } if (!metadata) { const sqlType = this.inferTypeFromPropertyName(propertyName); metadata = { type: sqlType, nullable: propertyName !== 'id', }; } properties[propertyName] = metadata; }); this.applyUniversalConstraints(properties); this.columnCache = properties; return properties; } inferColumnMetadata(entity, propertyName) { const value = entity[propertyName]; const propertyDescriptor = Object.getOwnPropertyDescriptor(entity, propertyName) || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(entity), propertyName); if (propertyDescriptor && (typeof propertyDescriptor.value === 'function' || propertyDescriptor.get || propertyDescriptor.set)) { return null; } let sqlType; let nullable = true; let defaultValue; if (typeof value === 'string') { sqlType = 'TEXT'; } else if (typeof value === 'number') { sqlType = 'INTEGER'; } else if (typeof value === 'boolean') { sqlType = 'INTEGER'; defaultValue = value ? 1 : 0; } else if (value instanceof Date) { sqlType = 'TEXT'; } else if (value === null || value === undefined) { sqlType = this.inferTypeFromPropertyName(propertyName); nullable = true; } else { sqlType = 'TEXT'; } return { type: sqlType, nullable, unique: false, default: defaultValue, }; } inferTypeFromPropertyName(propertyName) { const lowerName = propertyName.toLowerCase(); if (lowerName.includes('date') || lowerName.includes('time') || lowerName.endsWith('at')) { return 'TEXT'; } if (lowerName.includes('hash') || lowerName.includes('salt') || lowerName.includes('token')) { return 'TEXT'; } if (lowerName.includes('active') || lowerName.includes('enabled') || lowerName.includes('is') || lowerName.includes('has') || lowerName.includes('can') || lowerName.includes('should')) { return 'INTEGER'; } if (lowerName.includes('count') || lowerName.includes('number') || lowerName.includes('amount') || lowerName.includes('size') || lowerName.includes('length') || lowerName.includes('version')) { return 'INTEGER'; } return 'TEXT'; } applyUniversalConstraints(columns) { if (columns.id) { columns.id.type = 'TEXT PRIMARY KEY'; columns.id.nullable = false; } if (columns.dateCreated) { columns.dateCreated.type = 'TEXT NOT NULL'; columns.dateCreated.nullable = false; } if (columns.dateUpdated) { columns.dateUpdated.type = 'TEXT NOT NULL'; columns.dateUpdated.nullable = false; } if (columns.dateDeleted) { columns.dateDeleted.type = 'TEXT'; columns.dateDeleted.nullable = true; } if (columns.version) { columns.version.type = 'INTEGER DEFAULT 1'; columns.version.nullable = false; columns.version.default = 1; } } getEntityColumns() { const columnMetadata = this.analyzeEntityProperties(); const columns = {}; Object.entries(columnMetadata).forEach(([name, metadata]) => { columns[name] = metadata; }); return columns; } clearColumnCache() { this.columnCache = null; } getColumnMetadata() { return this.analyzeEntityProperties(); } hasColumn(columnName) { const metadata = this.analyzeEntityProperties(); return columnName in metadata; } initializeTable() { const columns = this.getEntityColumns(); const columnDefinitions = Object.entries(columns) .map(([name, metadata]) => `${name} ${metadata.type}`) .join(',\n '); const createTableSql = ` CREATE TABLE IF NOT EXISTS ${this.tableName} ( ${columnDefinitions} ) `; try { this.db.exec(createTableSql); } catch (err) { console.error(`SqliteRepositoryAdapter: Error creating ${this.tableName} table:`, err); throw err; } } async ensureColumnsExist(requiredColumns) { try { const tableInfoSql = `PRAGMA table_info(${this.tableName})`; const existingColumns = new Set(); const rows = await this.allQuery(tableInfoSql); if (rows) { rows.forEach((row) => { existingColumns.add(row.name); }); } const columnMetadata = this.analyzeEntityProperties(); for (const columnName of requiredColumns) { if (!existingColumns.has(columnName)) { let metadata = columnMetadata[columnName]; if (!metadata) { const sqlType = this.inferTypeFromPropertyName(columnName); metadata = { type: sqlType, nullable: columnName !== 'id', }; } const columnDef = `${columnName} ${metadata.type}`; const alterSql = `ALTER TABLE ${this.tableName} ADD COLUMN ${columnDef}`; try { await this.runQuery(alterSql); } catch (alterErr) { console.error(`Error adding column ${columnName}:`, alterErr); } } } } catch (err) { console.error('Error ensuring columns exist:', err); } } mapRowToEntity(row) { const entity = {}; for (const [key, value] of Object.entries(row)) { if (value !== null && value !== undefined) { const isDateProperty = key.toLowerCase().includes('date') || key.toLowerCase().includes('time') || key.toLowerCase().includes('at') || key.toLowerCase().includes('created') || key.toLowerCase().includes('updated') || key.toLowerCase().includes('deleted'); if (isDateProperty && typeof value === 'string' && !isNaN(Date.parse(value))) { entity[key] = new Date(value); } else if (key === 'active' && typeof value === 'number') { entity[key] = Boolean(value); } else if (key === 'version' && typeof value === 'number') { entity[key] = value; } else { entity[key] = value; } } else { entity[key] = value; } } return entity; } mapEntityToParams(entity) { const columns = this.getColumnNames(); const columnMetadata = this.analyzeEntityProperties(); return columns.map((column) => { const value = entity[column]; const metadata = columnMetadata[column]; if (value === null || value === undefined) { return null; } if (metadata) { if (metadata.type.includes('INTEGER')) { if (typeof value === 'boolean') { return value ? 1 : 0; } else { return Number(value); } } else if (value instanceof Date) { return value.toISOString(); } else { return value; } } else { if (typeof value === 'boolean') { return value ? 1 : 0; } else if (value instanceof Date) { return value.toISOString(); } else { return value; } } }); } getColumnNames() { return Object.keys(this.getEntityColumns()); } entityName() { return this.tableName; } generateId() { return Date.now().toString() + Math.random().toString(36).substr(2, 9); } runQuery(sql, params = []) { return new Promise((resolve, reject) => { this.db.run(sql, params, function (err) { if (err) { reject(err); } else { resolve({ lastID: this.lastID, changes: this.changes }); } }); }); } getQuery(sql, params = []) { return new Promise((resolve, reject) => { this.db.get(sql, params, (err, row) => { if (err) { reject(err); } else { resolve(row); } }); }); } allQuery(sql, params = []) { return new Promise((resolve, reject) => { this.db.all(sql, params, (err, rows) => { if (err) { reject(err); } else { resolve(rows); } }); }); } buildWhereClause(where, params) { const conditions = []; Object.entries(where).forEach(([key, value]) => { if (value !== undefined && value !== null) { if (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)) { const operators = value; if (operators.$gt !== undefined) { conditions.push(`${key} > ?`); params.push(operators.$gt); } if (operators.$gte !== undefined) { conditions.push(`${key} >= ?`); params.push(operators.$gte); } if (operators.$lt !== undefined) { conditions.push(`${key} < ?`); params.push(operators.$lt); } if (operators.$lte !== undefined) { conditions.push(`${key} <= ?`); params.push(operators.$lte); } } else { conditions.push(`${key} = ?`); params.push(value); } } else { conditions.push(`${key} IS NULL`); } }); return conditions.join(' AND '); } buildOrderClause(order) { const orderClauses = []; Object.entries(order).forEach(([key, direction]) => { orderClauses.push(`${key} ${direction}`); }); return orderClauses.join(', '); } async find(options) { let sql = `SELECT * FROM ${this.tableName}`; const params = []; if (options === null || options === void 0 ? void 0 : options.where) { const whereClause = this.buildWhereClause(options.where, params); if (whereClause) { sql += ` WHERE ${whereClause}`; } } if (options === null || options === void 0 ? void 0 : options.order) { const orderClause = this.buildOrderClause(options.order); if (orderClause) { sql += ` ORDER BY ${orderClause}`; } } if (options === null || options === void 0 ? void 0 : options.take) { sql += ` LIMIT ${options.take}`; } if (options === null || options === void 0 ? void 0 : options.skip) { sql += ` OFFSET ${options.skip}`; } const rows = await this.allQuery(sql, params); const entities = rows.map((row) => this.mapRowToEntity(row)); return entities; } async findOne(options) { const results = await this.find(Object.assign(Object.assign({}, options), { take: 1 })); const result = results.length > 0 ? results[0] : null; return result; } create(entityLike) { const entity = Object.assign({ id: this.generateId() }, entityLike); return entity; } merge(mergeIntoEntity, ...entityLikes) { return Object.assign(mergeIntoEntity, ...entityLikes); } async save(entity, options) { try { if (Array.isArray(entity)) { const savedEntities = []; for (const item of entity) { const savedEntity = (await this.save(item, options)); savedEntities.push(savedEntity); } return savedEntities; } const entityRecord = entity; if (!entityRecord.id) { entityRecord.id = this.generateId(); } const now = new Date(); if (!entityRecord.dateCreated) { entityRecord.dateCreated = now; } entityRecord.dateUpdated = now; if (entityRecord.version === null || entityRecord.version === undefined) { entityRecord.version = 1; } const cachedColumns = this.getColumnNames(); const entityColumns = Object.keys(entityRecord).filter((key) => entityRecord[key] !== undefined && !key.startsWith('_')); const allColumns = new Set([...entityColumns, ...cachedColumns]); const columns = Array.from(allColumns); await this.ensureColumnsExist(columns); const placeholders = columns.map(() => '?').join(', '); const sql = `INSERT OR REPLACE INTO ${this.tableName} (${columns.join(', ')}) VALUES (${placeholders})`; const params = columns.map((column) => { const value = entityRecord[column]; if (value === null || value === undefined) { return null; } if (typeof value === 'boolean') { return value ? 1 : 0; } else if (value instanceof Date) { return value.toISOString(); } else { return value; } }); await this.runQuery(sql, params); return entity; } catch (e) { console.error('Error in save method:', e); throw new nestjs_common_1.ModelQueryException(this.entityName(), { originalError: e, }); } } async remove(entity) { try { if (Array.isArray(entity)) { const removedEntities = []; for (const item of entity) { const removedEntity = (await this.remove(item)); removedEntities.push(removedEntity); } return removedEntities; } const entityRecord = entity; if (entityRecord.id) { const sql = `DELETE FROM ${this.tableName} WHERE id = ?`; await this.runQuery(sql, [entityRecord.id]); } return entity; } catch (e) { throw new nestjs_common_1.ModelQueryException(this.entityName(), { originalError: e, }); } } gt(value) { return { $gt: value }; } gte(value) { return { $gte: value }; } lt(value) { return { $lt: value }; } lte(value) { return { $lte: value }; } async close() { return new Promise((resolve, reject) => { this.db.close((err) => { if (err) { reject(err); } else { resolve(); } }); }); } } exports.SqliteRepositoryAdapter = SqliteRepositoryAdapter; //# sourceMappingURL=sqlite-repository.adapter.js.map