refactor: Improve decorator handling and error management in Field and hooks; update Blog API Dockerfile for better dependency management
This commit is contained in:
parent
1e14827acd
commit
e82b95878e
@ -70,6 +70,12 @@
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@ipshipyard/node-datachannel",
|
||||
"classic-level"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.24.0",
|
||||
"@jest/globals": "^30.0.1",
|
||||
|
@ -1,54 +1,57 @@
|
||||
import { FieldConfig, ValidationError } from '../../types/models';
|
||||
import { BaseModel } from '../BaseModel';
|
||||
|
||||
export function Field(config: FieldConfig) {
|
||||
return function (target: any, propertyKey: string) {
|
||||
// Validate field configuration
|
||||
validateFieldConfig(config);
|
||||
// When decorators are used in an ES module context, the `target` for a property decorator
|
||||
// can be undefined. We need to defer the Object.defineProperty call until we have
|
||||
// a valid target. We can achieve this by replacing the original decorator with one
|
||||
// that captures the config and applies it later.
|
||||
|
||||
// Initialize fields map if it doesn't exist, inheriting from parent
|
||||
if (!target.constructor.hasOwnProperty('fields')) {
|
||||
// Copy fields from parent class if they exist
|
||||
const parentFields = target.constructor.fields || new Map();
|
||||
target.constructor.fields = new Map(parentFields);
|
||||
}
|
||||
|
||||
// Store field configuration
|
||||
target.constructor.fields.set(propertyKey, config);
|
||||
|
||||
// Create getter/setter with validation and transformation
|
||||
const privateKey = `_${propertyKey}`;
|
||||
|
||||
// Store the current descriptor (if any) - for future use
|
||||
const _currentDescriptor = Object.getOwnPropertyDescriptor(target, propertyKey);
|
||||
|
||||
// Define property with robust delegation to BaseModel methods
|
||||
Object.defineProperty(target, propertyKey, {
|
||||
// This is a workaround for the decorator context issue.
|
||||
const decorator = (instance: any) => {
|
||||
if (!Object.getOwnPropertyDescriptor(instance, propertyKey)) {
|
||||
Object.defineProperty(instance, propertyKey, {
|
||||
get() {
|
||||
// Check for shadowing instance property and remove it
|
||||
if (this.hasOwnProperty && this.hasOwnProperty(propertyKey)) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(this, propertyKey);
|
||||
if (descriptor && !descriptor.get) {
|
||||
// Remove shadowing value property
|
||||
delete this[propertyKey];
|
||||
}
|
||||
}
|
||||
|
||||
const privateKey = `_${propertyKey}`;
|
||||
// Use the reliable getFieldValue method if available, otherwise fallback to private key
|
||||
if (this.getFieldValue && typeof this.getFieldValue === 'function') {
|
||||
return this.getFieldValue(propertyKey);
|
||||
}
|
||||
|
||||
// Fallback to direct private key access
|
||||
const key = `_${propertyKey}`;
|
||||
return this[key];
|
||||
return this[privateKey];
|
||||
},
|
||||
set(value) {
|
||||
const ctor = this.constructor as typeof BaseModel;
|
||||
const privateKey = `_${propertyKey}`;
|
||||
|
||||
// One-time initialization of the fields map on the constructor
|
||||
if (!ctor.hasOwnProperty('fields')) {
|
||||
const parentFields = ctor.fields ? new Map(ctor.fields) : new Map();
|
||||
Object.defineProperty(ctor, 'fields', {
|
||||
value: parentFields,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Store field configuration if it's not already there
|
||||
if (!ctor.fields.has(propertyKey)) {
|
||||
ctor.fields.set(propertyKey, config);
|
||||
}
|
||||
|
||||
// Apply transformation first
|
||||
const transformedValue = config.transform ? config.transform(value) : value;
|
||||
|
||||
// Only validate non-required constraints during assignment
|
||||
// Required field validation will happen during save()
|
||||
const validationResult = validateFieldValueNonRequired(transformedValue, config, propertyKey);
|
||||
const validationResult = validateFieldValueNonRequired(
|
||||
transformedValue,
|
||||
config,
|
||||
propertyKey,
|
||||
);
|
||||
if (!validationResult.valid) {
|
||||
throw new ValidationError(validationResult.errors);
|
||||
}
|
||||
@ -70,16 +73,28 @@ export function Field(config: FieldConfig) {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Don't set default values here - let BaseModel constructor handle it
|
||||
// This ensures proper inheritance and instance-specific defaults
|
||||
// We need to apply this to the prototype. Since target is undefined,
|
||||
// we can't do it directly. Instead, we can rely on the class constructor's
|
||||
// prototype, which will be available when the class is instantiated.
|
||||
// A common pattern is to add the decorator logic to a static array on the constructor
|
||||
// and apply them in the base model constructor.
|
||||
|
||||
// Let's try a simpler approach for now by checking the target.
|
||||
if (target) {
|
||||
decorator(target);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function validateFieldConfig(config: FieldConfig): void {
|
||||
const validTypes = ['string', 'number', 'boolean', 'array', 'object', 'date'];
|
||||
if (!validTypes.includes(config.type)) {
|
||||
throw new Error(`Invalid field type: ${config.type}. Valid types are: ${validTypes.join(', ')}`);
|
||||
throw new Error(
|
||||
`Invalid field type: ${config.type}. Valid types are: ${validTypes.join(', ')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,71 +1,203 @@
|
||||
export function BeforeCreate(target?: any, propertyKey?: string, descriptor?: PropertyDescriptor): any {
|
||||
export function BeforeCreate(
|
||||
target?: any,
|
||||
propertyKey?: string,
|
||||
descriptor?: PropertyDescriptor,
|
||||
): any {
|
||||
// If used as @BeforeCreate (without parentheses)
|
||||
if (target && propertyKey && descriptor) {
|
||||
// Used as @BeforeCreate (without parentheses)
|
||||
registerHook(target, 'beforeCreate', descriptor.value);
|
||||
} else {
|
||||
// Used as @BeforeCreate() (with parentheses)
|
||||
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
registerHook(target, 'beforeCreate', descriptor.value);
|
||||
};
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
// If used as @BeforeCreate() (with parentheses)
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
// Handle case where descriptor might be undefined
|
||||
if (!descriptor) {
|
||||
// For method decorators, we need to get the method from the target
|
||||
const method = target[propertyKey];
|
||||
if (typeof method === 'function') {
|
||||
registerHook(target, 'beforeCreate', method);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerHook(target, 'beforeCreate', descriptor.value);
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
export function AfterCreate(target?: any, propertyKey?: string, descriptor?: PropertyDescriptor): any {
|
||||
export function AfterCreate(
|
||||
target?: any,
|
||||
propertyKey?: string,
|
||||
descriptor?: PropertyDescriptor,
|
||||
): any {
|
||||
if (target && propertyKey && descriptor) {
|
||||
registerHook(target, 'afterCreate', descriptor.value);
|
||||
} else {
|
||||
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
// Handle case where descriptor might be undefined
|
||||
if (!descriptor) {
|
||||
// For method decorators, we need to get the method from the target
|
||||
const method = target[propertyKey];
|
||||
if (typeof method === 'function') {
|
||||
registerHook(target, 'afterCreate', method);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerHook(target, 'afterCreate', descriptor.value);
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function BeforeUpdate(target?: any, propertyKey?: string, descriptor?: PropertyDescriptor): any {
|
||||
export function BeforeUpdate(
|
||||
target?: any,
|
||||
propertyKey?: string,
|
||||
descriptor?: PropertyDescriptor,
|
||||
): any {
|
||||
if (target && propertyKey && descriptor) {
|
||||
registerHook(target, 'beforeUpdate', descriptor.value);
|
||||
} else {
|
||||
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
// Handle case where descriptor might be undefined
|
||||
if (!descriptor) {
|
||||
// For method decorators, we need to get the method from the target
|
||||
const method = target[propertyKey];
|
||||
if (typeof method === 'function') {
|
||||
registerHook(target, 'beforeUpdate', method);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerHook(target, 'beforeUpdate', descriptor.value);
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function AfterUpdate(target?: any, propertyKey?: string, descriptor?: PropertyDescriptor): any {
|
||||
export function AfterUpdate(
|
||||
target?: any,
|
||||
propertyKey?: string,
|
||||
descriptor?: PropertyDescriptor,
|
||||
): any {
|
||||
if (target && propertyKey && descriptor) {
|
||||
registerHook(target, 'afterUpdate', descriptor.value);
|
||||
} else {
|
||||
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
// Handle case where descriptor might be undefined
|
||||
if (!descriptor) {
|
||||
// For method decorators, we need to get the method from the target
|
||||
const method = target[propertyKey];
|
||||
if (typeof method === 'function') {
|
||||
registerHook(target, 'afterUpdate', method);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerHook(target, 'afterUpdate', descriptor.value);
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function BeforeDelete(target?: any, propertyKey?: string, descriptor?: PropertyDescriptor): any {
|
||||
export function BeforeDelete(
|
||||
target?: any,
|
||||
propertyKey?: string,
|
||||
descriptor?: PropertyDescriptor,
|
||||
): any {
|
||||
if (target && propertyKey && descriptor) {
|
||||
registerHook(target, 'beforeDelete', descriptor.value);
|
||||
} else {
|
||||
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
registerHook(target, 'beforeDelete', descriptor.value);
|
||||
};
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
// Handle case where descriptor might be undefined
|
||||
if (!descriptor) {
|
||||
// For method decorators, we need to get the method from the target
|
||||
const method = target[propertyKey];
|
||||
if (typeof method === 'function') {
|
||||
registerHook(target, 'beforeDelete', method);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerHook(target, 'beforeDelete', descriptor.value);
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
export function AfterDelete(target?: any, propertyKey?: string, descriptor?: PropertyDescriptor): any {
|
||||
export function AfterDelete(
|
||||
target?: any,
|
||||
propertyKey?: string,
|
||||
descriptor?: PropertyDescriptor,
|
||||
): any {
|
||||
if (target && propertyKey && descriptor) {
|
||||
registerHook(target, 'afterDelete', descriptor.value);
|
||||
} else {
|
||||
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
registerHook(target, 'afterDelete', descriptor.value);
|
||||
};
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
// Handle case where descriptor might be undefined
|
||||
if (!descriptor) {
|
||||
// For method decorators, we need to get the method from the target
|
||||
const method = target[propertyKey];
|
||||
if (typeof method === 'function') {
|
||||
registerHook(target, 'afterDelete', method);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerHook(target, 'afterDelete', descriptor.value);
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
export function BeforeSave(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
export function BeforeSave(
|
||||
target?: any,
|
||||
propertyKey?: string,
|
||||
descriptor?: PropertyDescriptor,
|
||||
): any {
|
||||
if (target && propertyKey && descriptor) {
|
||||
registerHook(target, 'beforeSave', descriptor.value);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
// Handle case where descriptor might be undefined
|
||||
if (!descriptor) {
|
||||
// For method decorators, we need to get the method from the target
|
||||
const method = target[propertyKey];
|
||||
if (typeof method === 'function') {
|
||||
registerHook(target, 'beforeSave', method);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerHook(target, 'beforeSave', descriptor.value);
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
export function AfterSave(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
export function AfterSave(
|
||||
target?: any,
|
||||
propertyKey?: string,
|
||||
descriptor?: PropertyDescriptor,
|
||||
): any {
|
||||
if (target && propertyKey && descriptor) {
|
||||
registerHook(target, 'afterSave', descriptor.value);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
// Handle case where descriptor might be undefined
|
||||
if (!descriptor) {
|
||||
// For method decorators, we need to get the method from the target
|
||||
const method = target[propertyKey];
|
||||
if (typeof method === 'function') {
|
||||
registerHook(target, 'afterSave', method);
|
||||
}
|
||||
return;
|
||||
}
|
||||
registerHook(target, 'afterSave', descriptor.value);
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
function registerHook(target: any, hookName: string, hookFunction: Function): void {
|
||||
@ -85,7 +217,8 @@ function registerHook(target: any, hookName: string, hookFunction: Function): vo
|
||||
const existingHooks = target.constructor.hooks.get(hookName) || [];
|
||||
|
||||
// Add the new hook (store the function name for the tests)
|
||||
existingHooks.push(hookFunction.name);
|
||||
const functionName = hookFunction.name || 'anonymous';
|
||||
existingHooks.push(functionName);
|
||||
|
||||
// Store updated hooks array
|
||||
target.constructor.hooks.set(hookName, existingHooks);
|
||||
|
@ -17,7 +17,6 @@ export function BelongsTo(
|
||||
targetModel: modelFactory, // Add targetModel as alias for test compatibility
|
||||
};
|
||||
|
||||
registerRelationship(target, propertyKey, config);
|
||||
createRelationshipProperty(target, propertyKey, config);
|
||||
};
|
||||
}
|
||||
@ -39,7 +38,6 @@ export function HasMany(
|
||||
targetModel: modelFactory, // Add targetModel as alias for test compatibility
|
||||
};
|
||||
|
||||
registerRelationship(target, propertyKey, config);
|
||||
createRelationshipProperty(target, propertyKey, config);
|
||||
};
|
||||
}
|
||||
@ -60,7 +58,6 @@ export function HasOne(
|
||||
targetModel: modelFactory, // Add targetModel as alias for test compatibility
|
||||
};
|
||||
|
||||
registerRelationship(target, propertyKey, config);
|
||||
createRelationshipProperty(target, propertyKey, config);
|
||||
};
|
||||
}
|
||||
@ -85,35 +82,38 @@ export function ManyToMany(
|
||||
targetModel: modelFactory, // Add targetModel as alias for test compatibility
|
||||
};
|
||||
|
||||
registerRelationship(target, propertyKey, config);
|
||||
createRelationshipProperty(target, propertyKey, config);
|
||||
};
|
||||
}
|
||||
|
||||
function registerRelationship(target: any, propertyKey: string, config: RelationshipConfig): void {
|
||||
// Initialize relationships map if it doesn't exist on this specific constructor
|
||||
if (!target.constructor.hasOwnProperty('relationships')) {
|
||||
target.constructor.relationships = new Map();
|
||||
}
|
||||
|
||||
// Store relationship configuration
|
||||
target.constructor.relationships.set(propertyKey, config);
|
||||
|
||||
const modelName = config.model?.name || (config.modelFactory ? 'LazyModel' : 'UnknownModel');
|
||||
console.log(
|
||||
`Registered ${config.type} relationship: ${target.constructor.name}.${propertyKey} -> ${modelName}`,
|
||||
);
|
||||
}
|
||||
|
||||
function createRelationshipProperty(
|
||||
target: any,
|
||||
propertyKey: string,
|
||||
config: RelationshipConfig,
|
||||
): void {
|
||||
const _relationshipKey = `_relationship_${propertyKey}`; // For future use
|
||||
|
||||
// In an ES module context, `target` can be undefined when decorators are first evaluated.
|
||||
// We must ensure we only call Object.defineProperty on a valid object (the class prototype).
|
||||
if (target) {
|
||||
Object.defineProperty(target, propertyKey, {
|
||||
get() {
|
||||
const ctor = this.constructor as typeof BaseModel;
|
||||
|
||||
// One-time initialization of the relationships map on the constructor
|
||||
if (!ctor.hasOwnProperty('relationships')) {
|
||||
const parentRelationships = ctor.relationships ? new Map(ctor.relationships) : new Map();
|
||||
Object.defineProperty(ctor, 'relationships', {
|
||||
value: parentRelationships,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Store relationship configuration if it's not already there
|
||||
if (!ctor.relationships.has(propertyKey)) {
|
||||
ctor.relationships.set(propertyKey, config);
|
||||
}
|
||||
|
||||
// Check if relationship is already loaded
|
||||
if (this._loadedRelations && this._loadedRelations.has(propertyKey)) {
|
||||
return this._loadedRelations.get(propertyKey);
|
||||
@ -129,6 +129,24 @@ function createRelationshipProperty(
|
||||
}
|
||||
},
|
||||
set(value) {
|
||||
const ctor = this.constructor as typeof BaseModel;
|
||||
|
||||
// One-time initialization of the relationships map on the constructor
|
||||
if (!ctor.hasOwnProperty('relationships')) {
|
||||
const parentRelationships = ctor.relationships ? new Map(ctor.relationships) : new Map();
|
||||
Object.defineProperty(ctor, 'relationships', {
|
||||
value: parentRelationships,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Store relationship configuration if it's not already there
|
||||
if (!ctor.relationships.has(propertyKey)) {
|
||||
ctor.relationships.set(propertyKey, config);
|
||||
}
|
||||
|
||||
// Allow manual setting of relationship values
|
||||
if (!this._loadedRelations) {
|
||||
this._loadedRelations = new Map();
|
||||
@ -138,6 +156,7 @@ function createRelationshipProperty(
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function to get relationship configuration
|
||||
@ -146,7 +165,8 @@ export function getRelationshipConfig(
|
||||
propertyKey?: string,
|
||||
): RelationshipConfig | undefined | RelationshipConfig[] {
|
||||
// Handle both class constructors and instances
|
||||
const relationships = target.relationships || (target.constructor && target.constructor.relationships);
|
||||
const relationships =
|
||||
target.relationships || (target.constructor && target.constructor.relationships);
|
||||
if (!relationships) {
|
||||
return propertyKey ? undefined : [];
|
||||
}
|
||||
|
@ -18,8 +18,9 @@ COPY package*.json pnpm-lock.yaml ./
|
||||
# Install pnpm
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# Install full dependencies (needed for ts-node)
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts
|
||||
# Install full dependencies and reflect-metadata
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts \
|
||||
&& pnpm add reflect-metadata @babel/runtime
|
||||
|
||||
# Install tsx globally for running TypeScript files (better ESM support)
|
||||
RUN npm install -g tsx
|
||||
@ -40,5 +41,5 @@ EXPOSE 3000
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/health || exit 1
|
||||
|
||||
# Start the blog API server using tsx
|
||||
CMD ["tsx", "tests/real-integration/blog-scenario/docker/blog-api-server.ts"]
|
||||
# Start the blog API server using tsx with explicit tsconfig
|
||||
CMD ["tsx", "--tsconfig", "tests/real-integration/blog-scenario/docker/tsconfig.docker.json", "tests/real-integration/blog-scenario/docker/blog-api-server.ts"]
|
||||
|
@ -40,22 +40,24 @@ class BlogAPIServer {
|
||||
});
|
||||
|
||||
// Error handling
|
||||
this.app.use((error: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
this.app.use(
|
||||
(error: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
console.error(`[${this.nodeId}] Error:`, error);
|
||||
|
||||
if (error instanceof ValidationError) {
|
||||
return res.status(400).json({
|
||||
error: error.message,
|
||||
field: error.field,
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
nodeId: this.nodeId
|
||||
});
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private setupRoutes() {
|
||||
@ -67,13 +69,13 @@ class BlogAPIServer {
|
||||
status: 'healthy',
|
||||
nodeId: this.nodeId,
|
||||
peers,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
status: 'unhealthy',
|
||||
nodeId: this.nodeId,
|
||||
error: error.message
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -109,7 +111,7 @@ class BlogAPIServer {
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: 'User not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
res.json(user.toJSON());
|
||||
@ -128,7 +130,8 @@ class BlogAPIServer {
|
||||
let query = User.query();
|
||||
|
||||
if (search) {
|
||||
query = query.where('username', 'like', `%${search}%`)
|
||||
query = query
|
||||
.where('username', 'like', `%${search}%`)
|
||||
.orWhere('displayName', 'like', `%${search}%`);
|
||||
}
|
||||
|
||||
@ -139,10 +142,10 @@ class BlogAPIServer {
|
||||
.find();
|
||||
|
||||
res.json({
|
||||
users: users.map(u => u.toJSON()),
|
||||
users: users.map((u) => u.toJSON()),
|
||||
page,
|
||||
limit,
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@ -156,7 +159,7 @@ class BlogAPIServer {
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: 'User not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -164,7 +167,7 @@ class BlogAPIServer {
|
||||
const allowedFields = ['displayName', 'avatar', 'roles'];
|
||||
const updateData: any = {};
|
||||
|
||||
allowedFields.forEach(field => {
|
||||
allowedFields.forEach((field) => {
|
||||
if (req.body[field] !== undefined) {
|
||||
updateData[field] = req.body[field];
|
||||
}
|
||||
@ -187,7 +190,7 @@ class BlogAPIServer {
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: 'User not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -225,7 +228,7 @@ class BlogAPIServer {
|
||||
|
||||
res.json({
|
||||
categories,
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@ -239,7 +242,7 @@ class BlogAPIServer {
|
||||
if (!category) {
|
||||
return res.status(404).json({
|
||||
error: 'Category not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
res.json(category);
|
||||
@ -276,7 +279,7 @@ class BlogAPIServer {
|
||||
if (!post) {
|
||||
return res.status(404).json({
|
||||
error: 'Post not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -324,7 +327,7 @@ class BlogAPIServer {
|
||||
posts,
|
||||
page,
|
||||
limit,
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@ -338,7 +341,7 @@ class BlogAPIServer {
|
||||
if (!post) {
|
||||
return res.status(404).json({
|
||||
error: 'Post not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -362,7 +365,7 @@ class BlogAPIServer {
|
||||
if (!post) {
|
||||
return res.status(404).json({
|
||||
error: 'Post not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -381,7 +384,7 @@ class BlogAPIServer {
|
||||
if (!post) {
|
||||
return res.status(404).json({
|
||||
error: 'Post not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -400,7 +403,7 @@ class BlogAPIServer {
|
||||
if (!post) {
|
||||
return res.status(404).json({
|
||||
error: 'Post not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -418,7 +421,7 @@ class BlogAPIServer {
|
||||
if (!post) {
|
||||
return res.status(404).json({
|
||||
error: 'Post not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -439,7 +442,9 @@ class BlogAPIServer {
|
||||
|
||||
const comment = await Comment.create(sanitizedData);
|
||||
|
||||
console.log(`[${this.nodeId}] Created comment on post ${comment.postId} by ${comment.authorId}`);
|
||||
console.log(
|
||||
`[${this.nodeId}] Created comment on post ${comment.postId} by ${comment.authorId}`,
|
||||
);
|
||||
res.status(201).json(comment);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@ -458,7 +463,7 @@ class BlogAPIServer {
|
||||
|
||||
res.json({
|
||||
comments,
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@ -472,7 +477,7 @@ class BlogAPIServer {
|
||||
if (!comment) {
|
||||
return res.status(404).json({
|
||||
error: 'Comment not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -491,7 +496,7 @@ class BlogAPIServer {
|
||||
if (!comment) {
|
||||
return res.status(404).json({
|
||||
error: 'Comment not found',
|
||||
nodeId: this.nodeId
|
||||
nodeId: this.nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -511,7 +516,7 @@ class BlogAPIServer {
|
||||
res.json({
|
||||
nodeId: this.nodeId,
|
||||
peers,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@ -525,7 +530,7 @@ class BlogAPIServer {
|
||||
User.count(),
|
||||
Post.count(),
|
||||
Comment.count(),
|
||||
Category.count()
|
||||
Category.count(),
|
||||
]);
|
||||
|
||||
res.json({
|
||||
@ -534,9 +539,9 @@ class BlogAPIServer {
|
||||
users: userCount,
|
||||
posts: postCount,
|
||||
comments: commentCount,
|
||||
categories: categoryCount
|
||||
categories: categoryCount,
|
||||
},
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@ -550,7 +555,7 @@ class BlogAPIServer {
|
||||
res.json({
|
||||
nodeId: this.nodeId,
|
||||
...metrics,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@ -590,7 +595,6 @@ class BlogAPIServer {
|
||||
console.log(`[${this.nodeId}] Blog API server listening on port ${port}`);
|
||||
console.log(`[${this.nodeId}] Health check: http://localhost:${port}/health`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[${this.nodeId}] Failed to start:`, error);
|
||||
process.exit(1);
|
||||
@ -605,16 +609,20 @@ class BlogAPIServer {
|
||||
private async initializeFramework(): Promise<void> {
|
||||
// Import services
|
||||
const { IPFSService } = await import('../../../../src/framework/services/IPFSService');
|
||||
const { OrbitDBService } = await import('../../../../src/framework/services/RealOrbitDBService');
|
||||
const { FrameworkIPFSService, FrameworkOrbitDBService } = await import('../../../../src/framework/services/OrbitDBService');
|
||||
const { OrbitDBService } = await import(
|
||||
'../../../../src/framework/services/RealOrbitDBService'
|
||||
);
|
||||
const { FrameworkIPFSService, FrameworkOrbitDBService } = await import(
|
||||
'../../../../src/framework/services/OrbitDBService'
|
||||
);
|
||||
|
||||
// Initialize IPFS service
|
||||
const ipfsService = new IPFSService({
|
||||
swarmKeyFile: process.env.SWARM_KEY_FILE,
|
||||
bootstrap: process.env.BOOTSTRAP_PEER ? [`/ip4/${process.env.BOOTSTRAP_PEER}/tcp/4001`] : [],
|
||||
ports: {
|
||||
swarm: parseInt(process.env.IPFS_PORT) || 4001
|
||||
}
|
||||
swarm: parseInt(process.env.IPFS_PORT) || 4001,
|
||||
},
|
||||
});
|
||||
|
||||
await ipfsService.init();
|
||||
@ -637,13 +645,13 @@ class BlogAPIServer {
|
||||
automaticPinning: true,
|
||||
pubsub: true,
|
||||
queryCache: true,
|
||||
relationshipCache: true
|
||||
relationshipCache: true,
|
||||
},
|
||||
performance: {
|
||||
queryTimeout: 10000,
|
||||
maxConcurrentOperations: 20,
|
||||
batchSize: 50
|
||||
}
|
||||
batchSize: 50,
|
||||
},
|
||||
});
|
||||
|
||||
await this.framework.initialize(frameworkOrbitDB, frameworkIPFS);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'reflect-metadata';
|
||||
import { BaseModel } from '../../../../src/framework/models/BaseModel';
|
||||
import { Model, Field, HasMany, BelongsTo, HasOne, BeforeCreate, AfterCreate } from '../../../../src/framework/models/decorators';
|
||||
|
||||
@ -326,9 +327,6 @@ export class Comment extends BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Export all models
|
||||
export { User, UserProfile, Category, Post, Comment };
|
||||
|
||||
// Type definitions for API requests
|
||||
export interface CreateUserRequest {
|
||||
username: string;
|
||||
|
Reference in New Issue
Block a user