mirror of
https://github.com/hexastack/hexabot
synced 2025-04-30 03:03:16 +00:00
feat: update DB schema to use subscriber.avatar
This commit is contained in:
parent
f27d7fb53c
commit
e2c81a9618
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -73,6 +73,7 @@ export class AttachmentService extends BaseService<Attachment> {
|
|||||||
/**
|
/**
|
||||||
* Downloads a user's profile picture either from a 3rd party storage system or from a local directory based on configuration.
|
* Downloads a user's profile picture either from a 3rd party storage system or from a local directory based on configuration.
|
||||||
*
|
*
|
||||||
|
* @deprecated Use AttachmentService.download() instead
|
||||||
* @param foreign_id The unique identifier of the user, used to locate the profile picture.
|
* @param foreign_id The unique identifier of the user, used to locate the profile picture.
|
||||||
* @returns A `StreamableFile` containing the user's profile picture.
|
* @returns A `StreamableFile` containing the user's profile picture.
|
||||||
*/
|
*/
|
||||||
@ -100,6 +101,7 @@ export class AttachmentService extends BaseService<Attachment> {
|
|||||||
/**
|
/**
|
||||||
* Uploads a profile picture to either 3rd party storage system or locally based on the configuration.
|
* Uploads a profile picture to either 3rd party storage system or locally based on the configuration.
|
||||||
*
|
*
|
||||||
|
* @deprecated use store() method instead
|
||||||
* @param res - The response object from which the profile picture will be buffered or piped.
|
* @param res - The response object from which the profile picture will be buffered or piped.
|
||||||
* @param filename - The filename
|
* @param filename - The filename
|
||||||
*/
|
*/
|
||||||
@ -157,6 +159,7 @@ export class AttachmentService extends BaseService<Attachment> {
|
|||||||
* Uploads files to the server. If a storage plugin is configured it uploads files accordingly.
|
* Uploads files to the server. If a storage plugin is configured it uploads files accordingly.
|
||||||
* Otherwise, uploads files to the local directory.
|
* Otherwise, uploads files to the local directory.
|
||||||
*
|
*
|
||||||
|
* @deprecated use store() instead
|
||||||
* @param files - An array of files to upload.
|
* @param files - An array of files to upload.
|
||||||
* @returns A promise that resolves to an array of uploaded attachments.
|
* @returns A promise that resolves to an array of uploaded attachments.
|
||||||
*/
|
*/
|
||||||
@ -241,19 +244,22 @@ export class AttachmentService extends BaseService<Attachment> {
|
|||||||
/**
|
/**
|
||||||
* Downloads an attachment identified by the provided parameters.
|
* Downloads an attachment identified by the provided parameters.
|
||||||
*
|
*
|
||||||
* @param attachment - The attachment to download.
|
* @param attachment - The attachment to download.
|
||||||
|
* @param rootDir - The root directory where attachment shoud be located.
|
||||||
* @returns A promise that resolves to a StreamableFile representing the downloaded attachment.
|
* @returns A promise that resolves to a StreamableFile representing the downloaded attachment.
|
||||||
*/
|
*/
|
||||||
async download(attachment: Attachment) {
|
async download(
|
||||||
|
attachment: Attachment,
|
||||||
|
rootDir = config.parameters.uploadDir,
|
||||||
|
) {
|
||||||
if (this.getStoragePlugin()) {
|
if (this.getStoragePlugin()) {
|
||||||
return await this.getStoragePlugin().download(attachment);
|
return await this.getStoragePlugin().download(attachment);
|
||||||
} else {
|
} else {
|
||||||
if (!fileExists(attachment.location)) {
|
const path = join(rootDir, attachment.location);
|
||||||
|
if (!fileExists(path)) {
|
||||||
throw new NotFoundException('No file was found');
|
throw new NotFoundException('No file was found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = join(config.parameters.uploadDir, attachment.location);
|
|
||||||
|
|
||||||
const disposition = `attachment; filename="${encodeURIComponent(
|
const disposition = `attachment; filename="${encodeURIComponent(
|
||||||
attachment.name,
|
attachment.name,
|
||||||
)}"`;
|
)}"`;
|
||||||
@ -272,18 +278,22 @@ export class AttachmentService extends BaseService<Attachment> {
|
|||||||
/**
|
/**
|
||||||
* Downloads an attachment identified by the provided parameters as a Buffer.
|
* Downloads an attachment identified by the provided parameters as a Buffer.
|
||||||
*
|
*
|
||||||
* @param attachment - The attachment to download.
|
* @param attachment - The attachment to download.
|
||||||
|
* @param rootDir - Root folder path where the attachment should be located.
|
||||||
* @returns A promise that resolves to a Buffer representing the downloaded attachment.
|
* @returns A promise that resolves to a Buffer representing the downloaded attachment.
|
||||||
*/
|
*/
|
||||||
async readAsBuffer(attachment: Attachment): Promise<Buffer> {
|
async readAsBuffer(
|
||||||
|
attachment: Attachment,
|
||||||
|
rootDir = config.parameters.uploadDir,
|
||||||
|
): Promise<Buffer> {
|
||||||
if (this.getStoragePlugin()) {
|
if (this.getStoragePlugin()) {
|
||||||
return await this.getStoragePlugin().readAsBuffer(attachment);
|
return await this.getStoragePlugin().readAsBuffer(attachment);
|
||||||
} else {
|
} else {
|
||||||
if (!fileExists(attachment.location)) {
|
const path = join(rootDir, attachment.location);
|
||||||
|
if (!fileExists(path)) {
|
||||||
throw new NotFoundException('No file was found');
|
throw new NotFoundException('No file was found');
|
||||||
}
|
}
|
||||||
const filePath = join(config.parameters.uploadDir, attachment.location);
|
return await fs.promises.readFile(path); // Reads the file content as a Buffer
|
||||||
return await fs.promises.readFile(filePath); // Reads the file content as a Buffer
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createReadStream, existsSync } from 'fs';
|
import { createReadStream, existsSync } from 'fs';
|
||||||
import { extname, join } from 'path';
|
import { extname } from 'path';
|
||||||
|
|
||||||
import { Logger, StreamableFile } from '@nestjs/common';
|
import { Logger, StreamableFile } from '@nestjs/common';
|
||||||
import { StreamableFileOptions } from '@nestjs/common/file-stream/interfaces/streamable-options.interface';
|
import { StreamableFileOptions } from '@nestjs/common/file-stream/interfaces/streamable-options.interface';
|
||||||
@ -29,20 +29,18 @@ export const isMime = (type: string): boolean => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a file exists in the specified upload directory.
|
* Checks if a file exists in the specified upload directory.
|
||||||
* @param location The relative location of the file.
|
* @param filePath The relative location of the file.
|
||||||
* @returns Whether the file exists.
|
* @returns True if the file exists.
|
||||||
*/
|
*/
|
||||||
export const fileExists = (location: string): boolean => {
|
export const fileExists = (filePath: string): boolean => {
|
||||||
// bypass test env
|
// bypass test env
|
||||||
if (config.env === 'test') {
|
if (config.env === 'test') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const dirPath = config.parameters.uploadDir;
|
return existsSync(filePath);
|
||||||
const fileLocation = join(dirPath, location);
|
|
||||||
return existsSync(fileLocation);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
new Logger(`Attachment Model : Unable to locate file: ${location}`);
|
new Logger(`Attachment Model : Unable to locate file: ${filePath}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -19,6 +19,8 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { CsrfCheck } from '@tekuconcept/nestjs-csrf';
|
import { CsrfCheck } from '@tekuconcept/nestjs-csrf';
|
||||||
|
|
||||||
|
import { AttachmentService } from '@/attachment/services/attachment.service';
|
||||||
|
import { config } from '@/config';
|
||||||
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
|
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
|
||||||
import { LoggerService } from '@/logger/logger.service';
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
import { Roles } from '@/utils/decorators/roles.decorator';
|
import { Roles } from '@/utils/decorators/roles.decorator';
|
||||||
@ -49,11 +51,21 @@ export class SubscriberController extends BaseController<
|
|||||||
> {
|
> {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly subscriberService: SubscriberService,
|
private readonly subscriberService: SubscriberService,
|
||||||
|
private readonly attachmentService: AttachmentService,
|
||||||
private readonly logger: LoggerService,
|
private readonly logger: LoggerService,
|
||||||
) {
|
) {
|
||||||
super(subscriberService);
|
super(subscriberService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a paginated list of subscribers based on provided query parameters.
|
||||||
|
* Supports filtering, pagination, and population of related fields.
|
||||||
|
*
|
||||||
|
* @param pageQuery - The pagination and sorting options.
|
||||||
|
* @param populate - List of fields to populate in the response.
|
||||||
|
* @param filters - Search filters to apply on the Subscriber model.
|
||||||
|
* @returns A promise containing the paginated and optionally populated list of subscribers.
|
||||||
|
*/
|
||||||
@Get()
|
@Get()
|
||||||
async findPage(
|
async findPage(
|
||||||
@Query(PageQueryPipe) pageQuery: PageQueryDto<Subscriber>,
|
@Query(PageQueryPipe) pageQuery: PageQueryDto<Subscriber>,
|
||||||
@ -79,8 +91,10 @@ export class SubscriberController extends BaseController<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Counts the filtered number of subscribers.
|
* Retrieves the count of subscribers that match the provided search filters.
|
||||||
* @returns A promise that resolves to an object representing the filtered number of subscribers.
|
*
|
||||||
|
* @param filters - Optional search filters to apply on the Subscriber model.
|
||||||
|
* @returns A promise containing the count of subscribers matching the filters.
|
||||||
*/
|
*/
|
||||||
@Get('count')
|
@Get('count')
|
||||||
async filterCount(
|
async filterCount(
|
||||||
@ -100,6 +114,14 @@ export class SubscriberController extends BaseController<
|
|||||||
return await this.count(filters);
|
return await this.count(filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a single subscriber by their unique ID.
|
||||||
|
* Supports optional population of related fields.
|
||||||
|
*
|
||||||
|
* @param id - The unique identifier of the subscriber to retrieve.
|
||||||
|
* @param populate - An optional list of related fields to populate in the response.
|
||||||
|
* @returns The subscriber document, populated if requested.
|
||||||
|
*/
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
async findOne(
|
async findOne(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@ -116,24 +138,29 @@ export class SubscriberController extends BaseController<
|
|||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profile picture (avatar) of a subscriber by their unique ID.
|
||||||
|
* If no avatar is set, generates an initials-based avatar.
|
||||||
|
*
|
||||||
|
* @param id - The unique identifier of the subscriber whose profile picture is to be retrieved.
|
||||||
|
* @returns A streamable file containing the avatar image.
|
||||||
|
*/
|
||||||
@Roles('public')
|
@Roles('public')
|
||||||
@Get(':foreign_id/profile_pic')
|
@Get(':id/profile_pic')
|
||||||
async findProfilePic(
|
async getAvatar(@Param('id') id: string): Promise<StreamableFile> {
|
||||||
@Param('foreign_id') foreign_id: string,
|
const subscriber = await this.subscriberService.findOneAndPopulate(id);
|
||||||
): Promise<StreamableFile> {
|
|
||||||
try {
|
if (!subscriber) {
|
||||||
const pic = await this.subscriberService.findProfilePic(foreign_id);
|
throw new NotFoundException(`Subscriber with ID ${id} not found`);
|
||||||
return pic;
|
|
||||||
} catch (e) {
|
|
||||||
const [subscriber] = await this.subscriberService.find({ foreign_id });
|
|
||||||
if (subscriber) {
|
|
||||||
return generateInitialsAvatar(subscriber);
|
|
||||||
} else {
|
|
||||||
throw new NotFoundException(
|
|
||||||
`Subscriber with ID ${foreign_id} not found`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subscriber.avatar) {
|
||||||
|
return this.attachmentService.download(
|
||||||
|
subscriber.avatar,
|
||||||
|
config.parameters.avatarDir,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return generateInitialsAvatar(subscriber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CsrfCheck(true)
|
@CsrfCheck(true)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -9,9 +9,7 @@
|
|||||||
import {
|
import {
|
||||||
Injectable,
|
Injectable,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
NotFoundException,
|
|
||||||
Optional,
|
Optional,
|
||||||
StreamableFile,
|
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
@ -139,22 +137,6 @@ export class SubscriberService extends BaseService<
|
|||||||
return await this.repository.handOverByForeignIdQuery(foreignId, userId);
|
return await this.repository.handOverByForeignIdQuery(foreignId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the profile picture of a subscriber based on the foreign ID.
|
|
||||||
*
|
|
||||||
* @param foreign_id - The foreign ID of the subscriber.
|
|
||||||
*
|
|
||||||
* @returns A streamable file representing the profile picture.
|
|
||||||
*/
|
|
||||||
async findProfilePic(foreign_id: string): Promise<StreamableFile> {
|
|
||||||
try {
|
|
||||||
return await this.attachmentService.downloadProfilePic(foreign_id);
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.error('Error downloading profile picture', err);
|
|
||||||
throw new NotFoundException('Profile picture not found');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply updates on end-user such as :
|
* Apply updates on end-user such as :
|
||||||
* - Assign labels to specific end-user
|
* - Assign labels to specific end-user
|
||||||
|
@ -53,7 +53,7 @@ export class MigrationService implements OnApplicationBootstrap {
|
|||||||
if (mongoose.connection.readyState !== 1) {
|
if (mongoose.connection.readyState !== 1) {
|
||||||
await this.connect();
|
await this.connect();
|
||||||
}
|
}
|
||||||
this.logger.log('Mongoose connection established');
|
this.logger.log('Mongoose connection established!');
|
||||||
|
|
||||||
if (!this.isCLI && config.mongo.autoMigrate) {
|
if (!this.isCLI && config.mongo.autoMigrate) {
|
||||||
this.logger.log('Executing migrations ...');
|
this.logger.log('Executing migrations ...');
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
|
import attachmentSchema, {
|
||||||
|
Attachment,
|
||||||
|
} from '@/attachment/schemas/attachment.schema';
|
||||||
|
import subscriberSchema, { Subscriber } from '@/chat/schemas/subscriber.schema';
|
||||||
|
|
||||||
|
import { MigrationServices } from '../types';
|
||||||
|
|
||||||
|
const populateSubscriberAvatar = async ({ logger }: MigrationServices) => {
|
||||||
|
const AttachmentModel = mongoose.model<Attachment>(
|
||||||
|
Attachment.name,
|
||||||
|
attachmentSchema,
|
||||||
|
);
|
||||||
|
const SubscriberModel = mongoose.model<Subscriber>(
|
||||||
|
Subscriber.name,
|
||||||
|
subscriberSchema,
|
||||||
|
);
|
||||||
|
|
||||||
|
const cursor = SubscriberModel.find().cursor();
|
||||||
|
|
||||||
|
for await (const subscriber of cursor) {
|
||||||
|
const foreignId = subscriber.foreign_id;
|
||||||
|
if (!foreignId) {
|
||||||
|
logger.debug(`No foreign id found for subscriber ${subscriber._id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachment = await AttachmentModel.findOne({
|
||||||
|
name: RegExp(`^${foreignId}.jpe?g$`),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (attachment) {
|
||||||
|
await SubscriberModel.updateOne(
|
||||||
|
{ _id: subscriber._id },
|
||||||
|
{ $set: { avatar: attachment._id } },
|
||||||
|
);
|
||||||
|
logger.log(
|
||||||
|
`Avatar attachment successfully updated for subscriber ${subscriber._id}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.debug(
|
||||||
|
`No avatar attachment found for subscriber ${subscriber._id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unpopulateSubscriberAvatar = async ({ logger }: MigrationServices) => {
|
||||||
|
const SubscriberModel = mongoose.model<Subscriber>(
|
||||||
|
Subscriber.name,
|
||||||
|
subscriberSchema,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rollback logic: unset the "avatar" field in all subscriber documents
|
||||||
|
const cursor = SubscriberModel.find({ avatar: { $exists: true } }).cursor();
|
||||||
|
|
||||||
|
for await (const subscriber of cursor) {
|
||||||
|
await SubscriberModel.updateOne(
|
||||||
|
{ _id: subscriber._id },
|
||||||
|
{ $set: { avatar: null } },
|
||||||
|
);
|
||||||
|
logger.log(
|
||||||
|
`Avatar attachment successfully updated for subscriber ${subscriber._id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async up(services: MigrationServices) {
|
||||||
|
await populateSubscriberAvatar(services);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
async down(services: MigrationServices) {
|
||||||
|
await unpopulateSubscriberAvatar(services);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -28,15 +28,22 @@ export abstract class BaseStoragePlugin extends BasePlugin {
|
|||||||
super(name, pluginService);
|
super(name, pluginService);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fileExists(attachment: Attachment): Promise<boolean>;
|
/** @deprecated use download() instead */
|
||||||
|
fileExists?(attachment: Attachment): Promise<boolean>;
|
||||||
|
|
||||||
abstract upload(file: Express.Multer.File): Promise<AttachmentCreateDto>;
|
/** @deprecated use store() instead */
|
||||||
|
upload?(file: Express.Multer.File): Promise<AttachmentCreateDto>;
|
||||||
|
|
||||||
abstract uploadAvatar(file: Express.Multer.File): Promise<any>;
|
/** @deprecated use store() instead */
|
||||||
|
uploadAvatar?(file: Express.Multer.File): Promise<any>;
|
||||||
|
|
||||||
abstract download(attachment: Attachment): Promise<StreamableFile>;
|
abstract download(
|
||||||
|
attachment: Attachment,
|
||||||
|
rootLocation?: string,
|
||||||
|
): Promise<StreamableFile>;
|
||||||
|
|
||||||
abstract downloadProfilePic(name: string): Promise<StreamableFile>;
|
/** @deprecated use download() instead */
|
||||||
|
downloadProfilePic?(name: string): Promise<StreamableFile>;
|
||||||
|
|
||||||
readAsBuffer?(attachment: Attachment): Promise<Buffer>;
|
readAsBuffer?(attachment: Attachment): Promise<Buffer>;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
@ -28,6 +28,7 @@ import { Request } from 'express';
|
|||||||
import { Session as ExpressSession } from 'express-session';
|
import { Session as ExpressSession } from 'express-session';
|
||||||
|
|
||||||
import { AttachmentService } from '@/attachment/services/attachment.service';
|
import { AttachmentService } from '@/attachment/services/attachment.service';
|
||||||
|
import { config } from '@/config';
|
||||||
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
|
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
|
||||||
import { LoggerService } from '@/logger/logger.service';
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
import { Roles } from '@/utils/decorators/roles.decorator';
|
import { Roles } from '@/utils/decorators/roles.decorator';
|
||||||
@ -83,7 +84,7 @@ export class ReadOnlyUserController extends BaseController<
|
|||||||
*/
|
*/
|
||||||
@Roles('public')
|
@Roles('public')
|
||||||
@Get('bot/profile_pic')
|
@Get('bot/profile_pic')
|
||||||
async botProfilePic(@Query('color') color: string) {
|
async getBotAvatar(@Query('color') color: string) {
|
||||||
return await getBotAvatar(color);
|
return await getBotAvatar(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,18 +97,20 @@ export class ReadOnlyUserController extends BaseController<
|
|||||||
*/
|
*/
|
||||||
@Roles('public')
|
@Roles('public')
|
||||||
@Get(':id/profile_pic')
|
@Get(':id/profile_pic')
|
||||||
async UserProfilePic(@Param('id') id: string) {
|
async getAvatar(@Param('id') id: string) {
|
||||||
try {
|
const user = await this.userService.findOneAndPopulate(id);
|
||||||
const res = await this.userService.userProfilePic(id);
|
if (!user) {
|
||||||
return res;
|
throw new NotFoundException(`user with ID ${id} not found`);
|
||||||
} catch (e) {
|
|
||||||
const user = await this.userService.findOne(id);
|
|
||||||
if (user) {
|
|
||||||
return await generateInitialsAvatar(user);
|
|
||||||
} else {
|
|
||||||
throw new NotFoundException(`user with ID ${id} not found`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.avatar) {
|
||||||
|
return await this.attachmentService.download(
|
||||||
|
user.avatar,
|
||||||
|
config.parameters.avatarDir,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateInitialsAvatar(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Hexastack. All rights reserved.
|
* Copyright © 2025 Hexastack. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { join } from 'path';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { Injectable, NotFoundException, StreamableFile } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { getStreamableFile } from '@/attachment/utilities';
|
|
||||||
import { config } from '@/config';
|
|
||||||
import { BaseService } from '@/utils/generics/base-service';
|
import { BaseService } from '@/utils/generics/base-service';
|
||||||
|
|
||||||
import { UserRepository } from '../repositories/user.repository';
|
import { UserRepository } from '../repositories/user.repository';
|
||||||
@ -22,33 +18,4 @@ export class UserService extends BaseService<User, UserPopulate, UserFull> {
|
|||||||
constructor(readonly repository: UserRepository) {
|
constructor(readonly repository: UserRepository) {
|
||||||
super(repository);
|
super(repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the user's profile picture as a streamable file.
|
|
||||||
*
|
|
||||||
* @param id - The ID of the user whose profile picture is requested.
|
|
||||||
*
|
|
||||||
* @returns A promise that resolves with the streamable file of the user's profile picture.
|
|
||||||
*/
|
|
||||||
async userProfilePic(id: string): Promise<StreamableFile> {
|
|
||||||
const user = await this.findOneAndPopulate(id);
|
|
||||||
if (user) {
|
|
||||||
const attachment = user.avatar;
|
|
||||||
const path = join(config.parameters.uploadDir, attachment.location);
|
|
||||||
const disposition = `attachment; filename="${encodeURIComponent(
|
|
||||||
attachment.name,
|
|
||||||
)}"`;
|
|
||||||
|
|
||||||
return getStreamableFile({
|
|
||||||
path,
|
|
||||||
options: {
|
|
||||||
type: attachment.type,
|
|
||||||
length: attachment.size,
|
|
||||||
disposition,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new NotFoundException('Profile Not found');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user