Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/nestjs-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
"nanoid": "3.3.7",
"nest-knexjs": "0.0.22",
"nestjs-cls": "4.3.0",
"nestjs-i18n": "10.5.1",
"nestjs-pino": "4.4.1",
"nestjs-redoc": "2.2.2",
"next": "14.2.14",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { JwtService } from '@nestjs/jwt';
import { generateUserId, getRandomString, HttpErrorCode, RandomType } from '@teable/core';
import { PrismaService } from '@teable/db-main-prisma';
import { MailTransporterType, MailType } from '@teable/openapi';
import { EmailVerifyCodeType, MailTransporterType, MailType } from '@teable/openapi';
import type { IChangePasswordRo, IInviteWaitlistVo, ISignup } from '@teable/openapi';
import * as bcrypt from 'bcrypt';
import { isEmpty } from 'lodash';
Expand Down Expand Up @@ -285,10 +285,6 @@ export class LocalAuthService {
const code = getRandomString(4, RandomType.Number);
const token = await this.jwtSignupCode(email, code);

if (this.baseConfig.enableEmailCodeConsole) {
console.info('Signup Verification code: ', '\x1b[34m' + code + '\x1b[0m');
}

const user = await this.userService.getUserByEmail(email);
this.isRegisteredValidate(user);

Expand All @@ -298,8 +294,9 @@ export class LocalAuthService {
);

const emailOptions = await this.mailSenderService.sendEmailVerifyCodeEmailOptions({
title: 'Signup verification',
message: `Your verification code is ${code}, expires in ${this.authConfig.signupVerificationExpiresIn}.`,
code,
expiresIn: this.authConfig.signupVerificationExpiresIn,
type: EmailVerifyCodeType.Signup,
});

await this.mailSenderService.sendMail(
Expand Down Expand Up @@ -482,12 +479,10 @@ export class LocalAuthService {
{ email, newEmail, code },
{ expiresIn: this.baseConfig.emailCodeExpiresIn }
);
if (this.baseConfig.enableEmailCodeConsole) {
console.info('Change Email Verification code: ', '\x1b[34m' + code + '\x1b[0m');
}
const emailOptions = await this.mailSenderService.sendEmailVerifyCodeEmailOptions({
title: 'Change Email verification',
message: `Your verification code is ${code}, expires in ${this.baseConfig.emailCodeExpiresIn}.`,
code,
expiresIn: this.baseConfig.emailCodeExpiresIn,
type: EmailVerifyCodeType.ChangeEmail,
});
await this.mailSenderService.sendMail(
{
Expand Down Expand Up @@ -550,12 +545,12 @@ export class LocalAuthService {
for (const item of updateList) {
const times = 10;
const code = await this.genWaitlistInviteCode(times);
const mailOptions = await this.mailSenderService.commonEmailOptions({
to: item.email,
title: 'Welcome',
message: `You're off the waitlist!, Here is your invite code: ${code}, it can be used ${times} times`,
buttonUrl: `${this.mailConfig.origin}/auth/signup?inviteCode=${code}`,
buttonText: 'Signup',
const mailOptions = await this.mailSenderService.waitlistInviteEmailOptions({
email: item.email,
code,
times,
name: 'Guest',
waitlistInviteUrl: `${this.mailConfig.origin}/auth/signup?inviteCode=${code}`,
});
res.push({
email: item.email,
Expand Down
4 changes: 2 additions & 2 deletions apps/nestjs-backend/src/features/auth/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { getPublicFullStorageUrl } from '../attachments/plugins/utils';

export type IPickUserMe = Pick<
Prisma.UserGetPayload<null>,
'id' | 'name' | 'avatar' | 'phone' | 'email' | 'password' | 'notifyMeta' | 'isAdmin'
'id' | 'name' | 'avatar' | 'phone' | 'email' | 'password' | 'notifyMeta' | 'isAdmin' | 'lang'
>;

export const pickUserMe = (user: IPickUserMe): IUserMeVo => {
return {
...pick(user, 'id', 'name', 'phone', 'email', 'isAdmin'),
...pick(user, 'id', 'name', 'phone', 'email', 'isAdmin', 'lang'),
notifyMeta: typeof user.notifyMeta === 'object' ? user.notifyMeta : JSON.parse(user.notifyMeta),
avatar:
user.avatar && !user.avatar?.startsWith('http')
Expand Down
30 changes: 24 additions & 6 deletions apps/nestjs-backend/src/features/base/base-export.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { Readable, PassThrough } from 'stream';
import { Injectable, Logger } from '@nestjs/common';
import type { ILinkFieldOptions } from '@teable/core';
import type { ILinkFieldOptions, ILocalization } from '@teable/core';
import { FieldType, getRandomString, ViewType, isLinkLookupOptions } from '@teable/core';
import type { Field, View, TableMeta, Base } from '@teable/db-main-prisma';
import { PrismaService } from '@teable/db-main-prisma';
Expand All @@ -19,6 +19,7 @@ import { IDbProvider } from '../../db-provider/db.provider.interface';
import { EventEmitterService } from '../../event-emitter/event-emitter.service';
import { Events } from '../../event-emitter/events';
import type { IClsStore } from '../../types/cls';
import type { I18nPath } from '../../types/i18n.generated';
import { second } from '../../utils/second';
import StorageAdapter from '../attachments/plugins/adapter';
import { InjectStorageAdapter } from '../attachments/plugins/storage';
Expand Down Expand Up @@ -92,13 +93,26 @@ export class BaseExportService {
'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(name)}`,
}
);
const messageString = `${baseName} Export successfully: <a href="${previewUrl}" name="${name}" class="hover:text-blue-500 underline">🗂️ ${name}</a>`;
this.notifyExportResult(baseId, messageString, previewUrl);
const message: ILocalization<I18nPath> = {
i18nKey: 'common.email.templates.notify.exportBase.success.message',
context: {
baseName,
previewUrl,
name,
},
};
this.notifyExportResult(baseId, message, previewUrl);
})
.catch(async (e) => {
this.logger.error(`export base zip error: ${e.message}`, e?.stack);
const messageString = `❌ ${baseName} export failed: ${e.message}`;
this.notifyExportResult(baseId, messageString);
const message: ILocalization<I18nPath> = {
i18nKey: 'common.email.templates.notify.exportBase.failed.message',
context: {
baseName,
errorMessage: e.message,
},
};
this.notifyExportResult(baseId, message);
});
}

Expand Down Expand Up @@ -992,7 +1006,11 @@ export class BaseExportService {
});
}

private async notifyExportResult(baseId: string, message: string, previewUrl?: string) {
private async notifyExportResult(
baseId: string,
message: string | ILocalization<I18nPath>,
previewUrl?: string
) {
const userId = this.cls.get('user.id');
await this.eventEmitterService.emit(Events.BASE_EXPORT_COMPLETE, {
previewUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
BadGatewayException,
BadRequestException,
} from '@nestjs/common';
import type { ILocalization } from '@teable/core';
import { generateCommentId, getCommentChannel, getTableCommentChannel } from '@teable/core';
import { PrismaService } from '@teable/db-main-prisma';
import type {
Expand All @@ -23,6 +24,7 @@ import { ClsService } from 'nestjs-cls';
import { CacheService } from '../../cache/cache.service';
import { ShareDbService } from '../../share-db/share-db.service';
import type { IClsStore } from '../../types/cls';
import type { I18nPath } from '../../types/i18n.generated';
import { AttachmentsStorageService } from '../attachments/attachments-storage.service';
import StorageAdapter from '../attachments/plugins/adapter';
import { getPublicFullStorageUrl } from '../attachments/plugins/utils';
Expand Down Expand Up @@ -653,15 +655,14 @@ export class CommentOpenApiService {
return;
}

const { name: baseName } =
(await this.prismaService.base.findFirst({
where: {
id: baseId,
},
select: {
name: true,
},
})) || {};
const { name: baseName } = await this.prismaService.base.findUniqueOrThrow({
where: {
id: baseId,
},
select: {
name: true,
},
});

const recordName = await this.recordService.getCellValue(tableId, recordId, fieldId);

Expand All @@ -679,7 +680,10 @@ export class CommentOpenApiService {
new Set([...notifyUsers.map(({ createdBy }) => createdBy), ...relativeUsers])
).filter((userId) => userId !== fromUserId);

const message = `${fromUserName} made a commented on ${recordName ? recordName : 'a record'} in ${tableName} ${baseName ? `in ${baseName}` : ''}`;
const message: ILocalization<I18nPath> = {
i18nKey: 'common.email.templates.notify.recordComment.message',
context: { fromUserName, recordName, tableName, baseName },
};

subscribeUsersIds.forEach((userId) => {
this.notificationService.sendCommentNotify({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { Readable } from 'stream';
import { Worker } from 'worker_threads';
import { InjectQueue, OnWorkerEvent, Processor, WorkerHost } from '@nestjs/bullmq';
import { Injectable, Logger } from '@nestjs/common';
import type { FieldType } from '@teable/core';
import type { FieldType, ILocalization } from '@teable/core';
import { getRandomString } from '@teable/core';
import { UploadType } from '@teable/openapi';
import type { IImportOptionRo, IImportColumn } from '@teable/openapi';
import { Job, Queue } from 'bullmq';
import Papa from 'papaparse';
import { EventEmitterService } from '../../../event-emitter/event-emitter.service';
import type { I18nPath } from '../../../types/i18n.generated';
import StorageAdapter from '../../attachments/plugins/adapter';
import { InjectStorageAdapter } from '../../attachments/plugins/storage';
import { NotificationService } from '../../notification/notification.service';
Expand Down Expand Up @@ -82,12 +83,22 @@ export class ImportTableCsvChunkQueueProcessor extends WorkerHost {
await this.resolveDataByWorker(job);
this.logger.log(`import data to ${table.id} chunk data job completed`);
} catch (error) {
let finalMessage = '';
let finalMessage: string | ILocalization<I18nPath> = '';
if (error instanceof ImportError && error.range) {
const range = error.range;
finalMessage = `❌ ${table.name} import aborted: ${error.message} fail row range: [${range[0]}, ${range[1]}]. Please check the data for this range and retry`;
finalMessage = {
i18nKey: 'common.email.templates.notify.import.table.aborted.message',
context: {
tableName: table.name,
errorMessage: error.message,
range: `${range[0]}, ${range[1]}`,
},
};
} else if (error instanceof Error) {
finalMessage = `❌ ${table.name} import failed: ${error.message}`;
finalMessage = {
i18nKey: 'common.email.templates.notify.import.table.failed.message',
context: { tableName: table.name, errorMessage: error.message },
};
}

if (notification && finalMessage) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,15 @@ export class ImportTableCsvQueueProcessor extends WorkerHost {
baseId,
tableId: table.id,
toUserId: userId,
message: `🎉 ${table.name} ${sourceColumnMap ? 'inplace' : ''} imported successfully`,
message: sourceColumnMap
? {
i18nKey: 'common.email.templates.notify.import.table.success.inplace',
context: { tableName: table.name },
}
: {
i18nKey: 'common.email.templates.notify.import.table.success.message',
context: { tableName: table.name },
},
});

this.eventEmitterService.emitAsync(Events.IMPORT_TABLE_COMPLETE, {
Expand Down Expand Up @@ -107,7 +115,14 @@ export class ImportTableCsvQueueProcessor extends WorkerHost {
baseId,
tableId: table.id,
toUserId: userId,
message: `❌ ${table.name} import aborted: ${err.message} fail row range: [${range}]. Please check the data for this range and retry.`,
message: {
i18nKey: 'common.email.templates.notify.import.table.aborted.message',
context: {
tableName: table.name,
errorMessage: err.message,
range: `${range[0]}, ${range[1]}`,
},
},
});

throw err;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
import { chunk, difference } from 'lodash';
import { ClsService } from 'nestjs-cls';

import { I18nContext } from 'nestjs-i18n';
import { ShareDbService } from '../../../share-db/share-db.service';
import type { IClsStore } from '../../../types/cls';
import { FieldOpenApiService } from '../../field/open-api/field-open-api.service';
Expand Down
Loading
Loading