mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
fix: message attachment id
This commit is contained in:
parent
1d896830cb
commit
d7cb39f9f4
@ -12,6 +12,7 @@ import { HttpModule } from '@nestjs/axios';
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { MongooseModule } from '@nestjs/mongoose';
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
|
|
||||||
|
import { AttachmentModule } from '@/attachment/attachment.module';
|
||||||
import { LoggerModule } from '@/logger/logger.module';
|
import { LoggerModule } from '@/logger/logger.module';
|
||||||
|
|
||||||
import { MigrationCommand } from './migration.command';
|
import { MigrationCommand } from './migration.command';
|
||||||
@ -23,6 +24,7 @@ import { MigrationService } from './migration.service';
|
|||||||
MongooseModule.forFeature([MigrationModel]),
|
MongooseModule.forFeature([MigrationModel]),
|
||||||
LoggerModule,
|
LoggerModule,
|
||||||
HttpModule,
|
HttpModule,
|
||||||
|
AttachmentModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
MigrationService,
|
MigrationService,
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
|
|||||||
import { getModelToken, MongooseModule } from '@nestjs/mongoose';
|
import { getModelToken, MongooseModule } from '@nestjs/mongoose';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
import { AttachmentService } from '@/attachment/services/attachment.service';
|
||||||
import { LoggerService } from '@/logger/logger.service';
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
import { MetadataRepository } from '@/setting/repositories/metadata.repository';
|
import { MetadataRepository } from '@/setting/repositories/metadata.repository';
|
||||||
import { Metadata, MetadataModel } from '@/setting/schemas/metadata.schema';
|
import { Metadata, MetadataModel } from '@/setting/schemas/metadata.schema';
|
||||||
@ -54,6 +55,10 @@ describe('MigrationService', () => {
|
|||||||
provide: HttpService,
|
provide: HttpService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: AttachmentService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: ModuleRef,
|
provide: ModuleRef,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -278,6 +283,7 @@ describe('MigrationService', () => {
|
|||||||
});
|
});
|
||||||
expect(loadMigrationFileSpy).toHaveBeenCalledWith('v2.1.9');
|
expect(loadMigrationFileSpy).toHaveBeenCalledWith('v2.1.9');
|
||||||
expect(migrationMock.up).toHaveBeenCalledWith({
|
expect(migrationMock.up).toHaveBeenCalledWith({
|
||||||
|
attachmentService: service['attachmentService'],
|
||||||
logger: service['logger'],
|
logger: service['logger'],
|
||||||
http: service['httpService'],
|
http: service['httpService'],
|
||||||
});
|
});
|
||||||
@ -308,6 +314,7 @@ describe('MigrationService', () => {
|
|||||||
});
|
});
|
||||||
expect(loadMigrationFileSpy).toHaveBeenCalledWith('v2.1.9');
|
expect(loadMigrationFileSpy).toHaveBeenCalledWith('v2.1.9');
|
||||||
expect(migrationMock.up).toHaveBeenCalledWith({
|
expect(migrationMock.up).toHaveBeenCalledWith({
|
||||||
|
attachmentService: service['attachmentService'],
|
||||||
logger: service['logger'],
|
logger: service['logger'],
|
||||||
http: service['httpService'],
|
http: service['httpService'],
|
||||||
});
|
});
|
||||||
@ -338,6 +345,7 @@ describe('MigrationService', () => {
|
|||||||
});
|
});
|
||||||
expect(loadMigrationFileSpy).toHaveBeenCalledWith('v2.1.9');
|
expect(loadMigrationFileSpy).toHaveBeenCalledWith('v2.1.9');
|
||||||
expect(migrationMock.up).toHaveBeenCalledWith({
|
expect(migrationMock.up).toHaveBeenCalledWith({
|
||||||
|
attachmentService: service['attachmentService'],
|
||||||
logger: service['logger'],
|
logger: service['logger'],
|
||||||
http: service['httpService'],
|
http: service['httpService'],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import leanDefaults from 'mongoose-lean-defaults';
|
|||||||
import leanGetters from 'mongoose-lean-getters';
|
import leanGetters from 'mongoose-lean-getters';
|
||||||
import leanVirtuals from 'mongoose-lean-virtuals';
|
import leanVirtuals from 'mongoose-lean-virtuals';
|
||||||
|
|
||||||
|
import { AttachmentService } from '@/attachment/services/attachment.service';
|
||||||
import { config } from '@/config';
|
import { config } from '@/config';
|
||||||
import { LoggerService } from '@/logger/logger.service';
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
import { MetadataService } from '@/setting/services/metadata.service';
|
import { MetadataService } from '@/setting/services/metadata.service';
|
||||||
@ -43,6 +44,7 @@ export class MigrationService implements OnApplicationBootstrap {
|
|||||||
private readonly logger: LoggerService,
|
private readonly logger: LoggerService,
|
||||||
private readonly metadataService: MetadataService,
|
private readonly metadataService: MetadataService,
|
||||||
private readonly httpService: HttpService,
|
private readonly httpService: HttpService,
|
||||||
|
private readonly attachmentService: AttachmentService,
|
||||||
@InjectModel(Migration.name)
|
@InjectModel(Migration.name)
|
||||||
private readonly migrationModel: Model<Migration>,
|
private readonly migrationModel: Model<Migration>,
|
||||||
) {}
|
) {}
|
||||||
@ -253,6 +255,7 @@ module.exports = {
|
|||||||
const result = await migration[action]({
|
const result = await migration[action]({
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
http: this.httpService,
|
http: this.httpService,
|
||||||
|
attachmentService: this.attachmentService,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
|||||||
@ -10,11 +10,13 @@ import { existsSync } from 'fs';
|
|||||||
import { join, resolve } from 'path';
|
import { join, resolve } from 'path';
|
||||||
|
|
||||||
import mongoose, { HydratedDocument } from 'mongoose';
|
import mongoose, { HydratedDocument } from 'mongoose';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import attachmentSchema, {
|
import attachmentSchema, {
|
||||||
Attachment,
|
Attachment,
|
||||||
} from '@/attachment/schemas/attachment.schema';
|
} from '@/attachment/schemas/attachment.schema';
|
||||||
import blockSchema, { Block } from '@/chat/schemas/block.schema';
|
import blockSchema, { Block } from '@/chat/schemas/block.schema';
|
||||||
|
import messageSchema, { Message } from '@/chat/schemas/message.schema';
|
||||||
import subscriberSchema, { Subscriber } from '@/chat/schemas/subscriber.schema';
|
import subscriberSchema, { Subscriber } from '@/chat/schemas/subscriber.schema';
|
||||||
import { StdOutgoingAttachmentMessage } from '@/chat/schemas/types/message';
|
import { StdOutgoingAttachmentMessage } from '@/chat/schemas/types/message';
|
||||||
import contentSchema, { Content } from '@/cms/schemas/content.schema';
|
import contentSchema, { Content } from '@/cms/schemas/content.schema';
|
||||||
@ -372,12 +374,114 @@ const migrateAttachmentContents = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates message documents that contain attachment "message.attachment"
|
||||||
|
* to apply one of the following operation:
|
||||||
|
* - Rename 'attachment_id' to 'id'
|
||||||
|
* - Parse internal url for to get the 'id'
|
||||||
|
* - Fetch external url, stores the attachment and store the 'id'
|
||||||
|
*
|
||||||
|
* @returns Resolves when the migration process is complete.
|
||||||
|
*/
|
||||||
|
const migrateAttachmentMessages = async ({
|
||||||
|
logger,
|
||||||
|
http,
|
||||||
|
attachmentService,
|
||||||
|
}: MigrationServices) => {
|
||||||
|
const MessageModel = mongoose.model<Message>(Message.name, messageSchema);
|
||||||
|
|
||||||
|
// Find blocks where "message.attachment" exists
|
||||||
|
const cursor = MessageModel.find({
|
||||||
|
'message.attachment.payload': { $exists: true },
|
||||||
|
'message.attachment.payload.id': { $exists: false },
|
||||||
|
}).cursor();
|
||||||
|
|
||||||
|
// Helper function to update the attachment ID in the database
|
||||||
|
const updateAttachmentId = async (
|
||||||
|
messageId: mongoose.Types.ObjectId,
|
||||||
|
attachmentId: string | null,
|
||||||
|
) => {
|
||||||
|
await MessageModel.updateOne(
|
||||||
|
{ _id: messageId },
|
||||||
|
{ $set: { 'message.attachment.payload.id': attachmentId } },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
for await (const msg of cursor) {
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
'attachment' in msg.message &&
|
||||||
|
'payload' in msg.message.attachment &&
|
||||||
|
msg.message.attachment.payload
|
||||||
|
) {
|
||||||
|
if ('attachment_id' in msg.message.attachment.payload) {
|
||||||
|
await updateAttachmentId(
|
||||||
|
msg._id,
|
||||||
|
msg.message.attachment.payload.attachment_id as string,
|
||||||
|
);
|
||||||
|
} else if ('url' in msg.message.attachment.payload) {
|
||||||
|
const url = msg.message.attachment.payload.url;
|
||||||
|
const regex =
|
||||||
|
/^https?:\/\/[\w.-]+\/attachment\/download\/([a-f\d]{24})\/.+$/;
|
||||||
|
// Test the URL and extract the ID
|
||||||
|
const match = url.match(regex);
|
||||||
|
if (match) {
|
||||||
|
const [, attachmentId] = match;
|
||||||
|
await updateAttachmentId(msg._id, attachmentId);
|
||||||
|
} else if (url) {
|
||||||
|
logger.log(
|
||||||
|
`Migrate message ${msg._id}: Handling an external url ...`,
|
||||||
|
);
|
||||||
|
const response = await http.axiosRef.get(url, {
|
||||||
|
responseType: 'arraybuffer', // Ensures the response is returned as a Buffer
|
||||||
|
});
|
||||||
|
const fileBuffer = Buffer.from(response.data);
|
||||||
|
const attachment = await attachmentService.store(fileBuffer, {
|
||||||
|
name: uuidv4(),
|
||||||
|
size: fileBuffer.length,
|
||||||
|
type: response.headers['content-type'],
|
||||||
|
channel: {},
|
||||||
|
});
|
||||||
|
await updateAttachmentId(msg._id, attachment.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
`Unable to migrate message ${msg._id}: No ID nor URL was found`,
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
'Unable to process message attachment: No ID or URL to be processed',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'Unable to process message attachment: Invalid Payload',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`Failed to update message ${msg._id}: ${error.message}, defaulting to null`,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await updateAttachmentId(msg._id, null);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(
|
||||||
|
`Failed to update message ${msg._id}: ${error.message}, unable to default to null`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async up(services: MigrationServices) {
|
async up(services: MigrationServices) {
|
||||||
await populateSubscriberAvatar(services);
|
await populateSubscriberAvatar(services);
|
||||||
await updateOldAvatarsPath(services);
|
await updateOldAvatarsPath(services);
|
||||||
await migrateAttachmentBlocks(MigrationAction.UP, services);
|
await migrateAttachmentBlocks(MigrationAction.UP, services);
|
||||||
await migrateAttachmentContents(MigrationAction.UP, services);
|
await migrateAttachmentContents(MigrationAction.UP, services);
|
||||||
|
// Given the complexity and inconsistency data, this method does not have
|
||||||
|
// a revert equivalent, at the same time, thus, it doesn't "unset" any attribute
|
||||||
|
await migrateAttachmentMessages(services);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
async down(services: MigrationServices) {
|
async down(services: MigrationServices) {
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import { HttpService } from '@nestjs/axios';
|
import { HttpService } from '@nestjs/axios';
|
||||||
|
|
||||||
|
import { AttachmentService } from '@/attachment/services/attachment.service';
|
||||||
import { LoggerService } from '@/logger/logger.service';
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
|
|
||||||
import { MigrationDocument } from './migration.schema';
|
import { MigrationDocument } from './migration.schema';
|
||||||
@ -34,4 +35,5 @@ export interface MigrationSuccessCallback extends MigrationRunParams {
|
|||||||
export type MigrationServices = {
|
export type MigrationServices = {
|
||||||
logger: LoggerService;
|
logger: LoggerService;
|
||||||
http: HttpService;
|
http: HttpService;
|
||||||
|
attachmentService: AttachmentService;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user