mirror of
https://github.com/docmost/docmost.git
synced 2026-08-29 18:14:58 +08:00
feat: implement space and workspace icons (#1558)
* feat: implement space and workspace icons - Create reusable AvatarUploader component supporting avatars, space icons, and workspace icons - Add Sharp package for server-side image resizing and optimization - Create reusable AvatarUploader component supporting avatars, space icons, and workspace icons - Support removing icons * add workspace logo support - add upload loader - add white background to transparent image - other fixes and enhancements * dark mode * fixes * cleanup
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
import { StorageService } from '../../../integrations/storage/storage.service';
|
||||
import { MultipartFile } from '@fastify/multipart';
|
||||
import {
|
||||
compressAndResizeIcon,
|
||||
getAttachmentFolderPath,
|
||||
PreparedFile,
|
||||
prepareFile,
|
||||
@@ -16,7 +17,7 @@ import { v4 as uuid4, v7 as uuid7 } from 'uuid';
|
||||
import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo';
|
||||
import { AttachmentType, validImageExtensions } from '../attachment.constants';
|
||||
import { KyselyDB, KyselyTransaction } from '@docmost/db/types/kysely.types';
|
||||
import { Attachment } from '@docmost/db/types/entity.types';
|
||||
import { Attachment, User, Workspace } from '@docmost/db/types/entity.types';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { executeTx } from '@docmost/db/utils';
|
||||
import { UserRepo } from '@docmost/db/repos/user/user.repo';
|
||||
@@ -132,8 +133,8 @@ export class AttachmentService {
|
||||
filePromise: Promise<MultipartFile>,
|
||||
type:
|
||||
| AttachmentType.Avatar
|
||||
| AttachmentType.WorkspaceLogo
|
||||
| AttachmentType.SpaceLogo,
|
||||
| AttachmentType.WorkspaceIcon
|
||||
| AttachmentType.SpaceIcon,
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
spaceId?: string,
|
||||
@@ -141,6 +142,9 @@ export class AttachmentService {
|
||||
const preparedFile: PreparedFile = await prepareFile(filePromise);
|
||||
validateFileType(preparedFile.fileExtension, validImageExtensions);
|
||||
|
||||
const processedBuffer = await compressAndResizeIcon(preparedFile.buffer, type);
|
||||
preparedFile.buffer = processedBuffer;
|
||||
preparedFile.fileSize = processedBuffer.length;
|
||||
preparedFile.fileName = uuid4() + preparedFile.fileExtension;
|
||||
|
||||
const filePath = `${getAttachmentFolderPath(type, workspaceId)}/${preparedFile.fileName}`;
|
||||
@@ -174,7 +178,7 @@ export class AttachmentService {
|
||||
workspaceId,
|
||||
trx,
|
||||
);
|
||||
} else if (type === AttachmentType.WorkspaceLogo) {
|
||||
} else if (type === AttachmentType.WorkspaceIcon) {
|
||||
const workspace = await this.workspaceRepo.findById(workspaceId, {
|
||||
trx,
|
||||
});
|
||||
@@ -186,7 +190,7 @@ export class AttachmentService {
|
||||
workspaceId,
|
||||
trx,
|
||||
);
|
||||
} else if (type === AttachmentType.SpaceLogo && spaceId) {
|
||||
} else if (type === AttachmentType.SpaceIcon && spaceId) {
|
||||
const space = await this.spaceRepo.findById(spaceId, workspaceId, {
|
||||
trx,
|
||||
});
|
||||
@@ -205,7 +209,6 @@ export class AttachmentService {
|
||||
});
|
||||
} catch (err) {
|
||||
// delete uploaded file on db update failure
|
||||
this.logger.error('Image upload error:', err);
|
||||
await this.deleteRedundantFile(filePath);
|
||||
throw new BadRequestException('Failed to upload image');
|
||||
}
|
||||
@@ -389,4 +392,40 @@ export class AttachmentService {
|
||||
}
|
||||
}
|
||||
|
||||
async removeUserAvatar(user: User) {
|
||||
if (user.avatarUrl && !user.avatarUrl.toLowerCase().startsWith('http')) {
|
||||
const filePath = `${getAttachmentFolderPath(AttachmentType.Avatar, user.workspaceId)}/${user.avatarUrl}`;
|
||||
await this.deleteRedundantFile(filePath);
|
||||
}
|
||||
|
||||
await this.userRepo.updateUser(
|
||||
{ avatarUrl: null },
|
||||
user.id,
|
||||
user.workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
async removeSpaceIcon(spaceId: string, workspaceId: string) {
|
||||
const space = await this.spaceRepo.findById(spaceId, workspaceId);
|
||||
|
||||
if (!space) {
|
||||
throw new NotFoundException('Space not found');
|
||||
}
|
||||
|
||||
if (space.logo && !space.logo.toLowerCase().startsWith('http')) {
|
||||
const filePath = `${getAttachmentFolderPath(AttachmentType.SpaceIcon, workspaceId)}/${space.logo}`;
|
||||
await this.deleteRedundantFile(filePath);
|
||||
}
|
||||
|
||||
await this.spaceRepo.updateSpace({ logo: null }, spaceId, workspaceId);
|
||||
}
|
||||
|
||||
async removeWorkspaceIcon(workspace: Workspace) {
|
||||
if (workspace.logo && !workspace.logo.toLowerCase().startsWith('http')) {
|
||||
const filePath = `${getAttachmentFolderPath(AttachmentType.WorkspaceIcon, workspace.id)}/${workspace.logo}`;
|
||||
await this.deleteRedundantFile(filePath);
|
||||
}
|
||||
|
||||
await this.workspaceRepo.updateWorkspace({ logo: null }, workspace.id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user