Skip to content

Commit 8ada7a8

Browse files
authored
feat: enterprise adapter (#1406)
* feat: enterprise adapter * fix: email * docs: additional Terms
1 parent 9a20985 commit 8ada7a8

File tree

98 files changed

+688
-634
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+688
-634
lines changed

AGPL_LICENSE

+18
Original file line numberDiff line numberDiff line change
@@ -659,3 +659,21 @@ specific requirements.
659659
if any, to sign a "copyright disclaimer" for the program, if necessary.
660660
For more information on this, and how to apply and follow the GNU AGPL, see
661661
<https://www.gnu.org/licenses/>.
662+
663+
Additional Terms under GNU Affero General Public License Version 3 (AGPLv3)
664+
665+
In accordance with Section 7 of the GNU Affero General Public License Version 3,
666+
the following additional terms apply to this software:
667+
668+
Brand Protection (Under Section 7(e)):
669+
670+
The Teable brand assets (including but not limited to the Teable name, logo,
671+
icons, and visual identity elements) are protected intellectual property and
672+
are not covered by the AGPLv3 license. While the software code may be modified
673+
under the terms of AGPL, any modification, replacement, or removal of these
674+
brand assets is explicitly prohibited.
675+
676+
Specifically:
677+
1. You may not modify or replace the Teable brand assets
678+
2. You may not remove the Teable brand assets
679+
3. You may not use the brand assets in a way that suggests endorsement

LICENSE

+18
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,21 @@ This project is a combination of components under different licenses to balance
1717
As a whole, this project is primarily under the AGPL-3.0 license, with the exception of the packages in the 'packages' directory, which are available under the more permissive MIT License to facilitate wider adoption and integration.
1818

1919
For any questions regarding licensing, please contact [email protected]
20+
21+
Additional Terms under GNU Affero General Public License Version 3 (AGPLv3)
22+
23+
In accordance with Section 7 of the GNU Affero General Public License Version 3,
24+
the following additional terms apply to this software:
25+
26+
Brand Protection (Under Section 7(e)):
27+
28+
The Teable brand assets (including but not limited to the Teable name, logo,
29+
icons, and visual identity elements) are protected intellectual property and
30+
are not covered by the AGPLv3 license. While the software code may be modified
31+
under the terms of AGPL, any modification, replacement, or removal of these
32+
brand assets is explicitly prohibited.
33+
34+
Specifically:
35+
1. You may not modify or replace the Teable brand assets
36+
2. You may not remove the Teable brand assets
37+
3. You may not use the brand assets in a way that suggests endorsement

apps/nestjs-backend/src/configs/auth.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const authConfig = registerAs('auth', () => ({
1717
expiresIn: process.env.BACKEND_SESSION_EXPIRES_IN ?? '7d',
1818
},
1919
accessToken: {
20-
prefix: process.env.BRAND_NAME!.toLocaleLowerCase(),
20+
prefix: 'teable',
2121
encryption: {
2222
algorithm: process.env.BACKEND_ACCESS_TOKEN_ENCRYPTION_ALGORITHM ?? 'aes-128-cbc',
2323
key: process.env.BACKEND_ACCESS_TOKEN_ENCRYPTION_KEY ?? 'ie21hOKjlXUiGDx9',

apps/nestjs-backend/src/configs/base.config.ts

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { registerAs } from '@nestjs/config';
55

66
export const baseConfig = registerAs('base', () => ({
77
isCloud: process.env.NEXT_BUILD_ENV_EDITION?.toUpperCase() === 'CLOUD',
8-
brandName: process.env.BRAND_NAME,
98
publicOrigin: process.env.PUBLIC_ORIGIN,
109
storagePrefix: process.env.STORAGE_PREFIX ?? process.env.PUBLIC_ORIGIN,
1110
secretKey: process.env.SECRET_KEY ?? 'defaultSecretKey',

apps/nestjs-backend/src/configs/env.validation.schema.ts

-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ export const envValidationSchema = Joi.object({
1919

2020
PUBLIC_ORIGIN: Joi.string().uri().required(),
2121

22-
BRAND_NAME: Joi.string().required(),
23-
2422
// cache
2523
BACKEND_CACHE_PROVIDER: Joi.string().valid('memory', 'sqlite', 'redis').default('sqlite'),
2624
// cache-sqlite

apps/nestjs-backend/src/features/attachments/plugins/adapter.ts

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default abstract class StorageAdapter {
1717
case UploadType.OAuth:
1818
case UploadType.Form:
1919
case UploadType.Plugin:
20+
case UploadType.Logo:
2021
return storageConfig().publicBucket;
2122
case UploadType.Comment:
2223
return storageConfig().privateBucket;
@@ -41,6 +42,8 @@ export default abstract class StorageAdapter {
4142
return 'plugin';
4243
case UploadType.Comment:
4344
return 'comment';
45+
case UploadType.Logo:
46+
return 'logo';
4447
default:
4548
throw new BadRequestException('Invalid upload type');
4649
}

apps/nestjs-backend/src/features/auth/local-auth/local-auth.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
22
import { JwtModule } from '@nestjs/jwt';
33
import type { IAuthConfig } from '../../../configs/auth.config';
44
import { authConfig } from '../../../configs/auth.config';
5+
import { MailSenderModule } from '../../mail-sender/mail-sender.module';
56
import { UserModule } from '../../user/user.module';
67
import { SessionStoreService } from '../session/session-store.service';
78
import { SessionModule } from '../session/session.module';
@@ -13,6 +14,7 @@ import { LocalAuthService } from './local-auth.service';
1314
imports: [
1415
UserModule,
1516
SessionModule,
17+
MailSenderModule.register(),
1618
JwtModule.registerAsync({
1719
useFactory: (config: IAuthConfig) => ({
1820
secret: config.jwt.secret,

apps/nestjs-backend/src/features/auth/local-auth/local-auth.service.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export class LocalAuthService {
178178
}
179179
const user = await this.userService.getUserByEmail(email);
180180
this.isRegisteredValidate(user);
181-
const emailOptions = this.mailSenderService.sendEmailVerifyCodeEmailOptions({
181+
const emailOptions = await this.mailSenderService.sendEmailVerifyCodeEmailOptions({
182182
title: 'Signup verification',
183183
message: `Your verification code is ${code}, expires in ${this.authConfig.signupVerificationExpiresIn}.`,
184184
});
@@ -223,7 +223,7 @@ export class LocalAuthService {
223223
const resetPasswordCode = getRandomString(30);
224224

225225
const url = `${this.mailConfig.origin}/auth/reset-password?code=${resetPasswordCode}`;
226-
const resetPasswordEmailOptions = this.mailSenderService.resetPasswordEmailOptions({
226+
const resetPasswordEmailOptions = await this.mailSenderService.resetPasswordEmailOptions({
227227
name: user.name,
228228
email: user.email,
229229
resetPasswordUrl: url,
@@ -333,7 +333,7 @@ export class LocalAuthService {
333333
if (this.baseConfig.enableEmailCodeConsole) {
334334
console.info('Change Email Verification code: ', '\x1b[34m' + code + '\x1b[0m');
335335
}
336-
const emailOptions = this.mailSenderService.sendEmailVerifyCodeEmailOptions({
336+
const emailOptions = await this.mailSenderService.sendEmailVerifyCodeEmailOptions({
337337
title: 'Change Email verification',
338338
message: `Your verification code is ${code}, expires in ${this.baseConfig.emailCodeExpiresIn}.`,
339339
});

apps/nestjs-backend/src/features/invitation/invitation.module.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { Module } from '@nestjs/common';
22
import { CollaboratorModule } from '../collaborator/collaborator.module';
3+
import { MailSenderModule } from '../mail-sender/mail-sender.module';
4+
import { SettingModule } from '../setting/setting.module';
35
import { UserModule } from '../user/user.module';
46
import { InvitationController } from './invitation.controller';
57
import { InvitationService } from './invitation.service';
68

79
@Module({
8-
imports: [CollaboratorModule, UserModule],
10+
imports: [SettingModule, CollaboratorModule, UserModule, MailSenderModule.register()],
911
providers: [InvitationService],
1012
exports: [InvitationService],
1113
controllers: [InvitationController],

apps/nestjs-backend/src/features/invitation/invitation.service.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ import type { IClsStore } from '../../types/cls';
2525
import { generateInvitationCode } from '../../utils/code-generate';
2626
import { CollaboratorService } from '../collaborator/collaborator.service';
2727
import { MailSenderService } from '../mail-sender/mail-sender.service';
28+
import { SettingService } from '../setting/setting.service';
2829
import { UserService } from '../user/user.service';
2930

3031
@Injectable()
3132
export class InvitationService {
3233
constructor(
3334
private readonly prismaService: PrismaService,
35+
private readonly settingService: SettingService,
3436
private readonly cls: ClsService<IClsStore>,
3537
private readonly configService: ConfigService,
3638
private readonly mailSenderService: MailSenderService,
@@ -152,8 +154,11 @@ export class InvitationService {
152154
invitationId: id,
153155
},
154156
});
157+
const { brandName } = await this.settingService.getServerBrand();
158+
155159
// get email info
156-
const inviteEmailOptions = this.mailSenderService.inviteEmailOptions({
160+
const inviteEmailOptions = await this.mailSenderService.inviteEmailOptions({
161+
brandName,
157162
name: user.name,
158163
email: user.email,
159164
resourceName,

apps/nestjs-backend/src/features/mail-sender/mail-helpers.ts

-5
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ import type { ConfigService } from '@nestjs/config';
22

33
export const helpers = (config: ConfigService) => {
44
const publicOrigin = config.get<string>('PUBLIC_ORIGIN');
5-
const brandName = config.get<string>('BRAND_NAME');
6-
75
return {
86
publicOrigin: function () {
97
return publicOrigin;
108
},
11-
brandName: function () {
12-
return brandName;
13-
},
149
currentYear: function () {
1510
return new Date().getFullYear();
1611
},

apps/nestjs-backend/src/features/mail-sender/mail-sender.module.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ConfigService } from '@nestjs/config';
66
import { MailerModule } from '@nestjs-modules/mailer';
77
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
88
import type { IMailConfig } from '../../configs/mail.config';
9+
import { SettingModule } from '../setting/setting.module';
910
import { helpers } from './mail-helpers';
1011
import { MailSenderService } from './mail-sender.service';
1112

@@ -18,9 +19,7 @@ export const { ConfigurableModuleClass: MailSenderModuleClass, OPTIONS_TYPE } =
1819

1920
@Module({})
2021
export class MailSenderModule extends MailSenderModuleClass {
21-
static register(options?: typeof OPTIONS_TYPE): DynamicModule {
22-
const { global } = options || {};
23-
22+
static register(): DynamicModule {
2423
const module = MailerModule.forRootAsync({
2524
inject: [ConfigService],
2625
useFactory: (config: ConfigService) => {
@@ -63,9 +62,8 @@ export class MailSenderModule extends MailSenderModuleClass {
6362
});
6463

6564
return {
66-
imports: [module],
65+
imports: [SettingModule, module],
6766
module: MailSenderModule,
68-
global,
6967
providers: [MailSenderService],
7068
exports: [MailSenderService],
7169
};

apps/nestjs-backend/src/features/mail-sender/mail-sender.service.spec.ts

-21
This file was deleted.

apps/nestjs-backend/src/features/mail-sender/mail-sender.service.ts

+23-14
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Injectable, Logger } from '@nestjs/common';
22
import type { ISendMailOptions } from '@nestjs-modules/mailer';
33
import { MailerService } from '@nestjs-modules/mailer';
44
import { CollaboratorType } from '@teable/openapi';
5-
import { BaseConfig, IBaseConfig } from '../../configs/base.config';
65
import { IMailConfig, MailConfig } from '../../configs/mail.config';
6+
import { SettingService } from '../setting/setting.service';
77

88
@Injectable()
99
export class MailSenderService {
@@ -12,7 +12,7 @@ export class MailSenderService {
1212
constructor(
1313
private readonly mailService: MailerService,
1414
@MailConfig() private readonly mailConfig: IMailConfig,
15-
@BaseConfig() private readonly baseConfig: IBaseConfig
15+
private readonly settingService: SettingService
1616
) {}
1717

1818
async sendMail(
@@ -35,15 +35,17 @@ export class MailSenderService {
3535

3636
inviteEmailOptions(info: {
3737
name: string;
38+
brandName: string;
3839
email: string;
3940
resourceName: string;
4041
resourceType: CollaboratorType;
4142
inviteUrl: string;
4243
}) {
43-
const { name, email, inviteUrl, resourceName, resourceType } = info;
44+
const { name, email, inviteUrl, resourceName, resourceType, brandName } = info;
4445
const resourceAlias = resourceType === CollaboratorType.Space ? 'Space' : 'Base';
46+
4547
return {
46-
subject: `${name} (${email}) invited you to their ${resourceAlias} ${resourceName} - ${this.baseConfig.brandName}`,
48+
subject: `${name} (${email}) invited you to their ${resourceAlias} ${resourceName} - ${brandName}`,
4749
template: 'normal',
4850
context: {
4951
name,
@@ -52,11 +54,12 @@ export class MailSenderService {
5254
resourceAlias,
5355
inviteUrl,
5456
partialBody: 'invite',
57+
brandName,
5558
},
5659
};
5760
}
5861

59-
collaboratorCellTagEmailOptions(info: {
62+
async collaboratorCellTagEmailOptions(info: {
6063
notifyId: string;
6164
fromUserName: string;
6265
refRecord: {
@@ -76,7 +79,7 @@ export class MailSenderService {
7679
const refLength = recordIds.length;
7780

7881
const viewRecordUrlPrefix = `${this.mailConfig.origin}/base/${baseId}/${tableId}`;
79-
82+
const { brandName } = await this.settingService.getServerBrand();
8083
if (refLength <= 1) {
8184
subject = `${fromUserName} added you to the ${fieldName} field of a record in ${tableName}`;
8285
partialBody = 'collaborator-cell-tag';
@@ -87,7 +90,7 @@ export class MailSenderService {
8790

8891
return {
8992
notifyMessage: subject,
90-
subject: `${subject} - ${this.baseConfig.brandName}`,
93+
subject: `${subject} - ${brandName}`,
9194
template: 'normal',
9295
context: {
9396
notifyId,
@@ -98,51 +101,57 @@ export class MailSenderService {
98101
recordIds,
99102
viewRecordUrlPrefix,
100103
partialBody,
104+
brandName,
101105
},
102106
};
103107
}
104108

105-
commonEmailOptions(info: {
109+
async commonEmailOptions(info: {
106110
to: string;
107111
title: string;
108112
message: string;
109113
buttonUrl: string;
110114
buttonText: string;
111115
}) {
112116
const { title, message } = info;
113-
117+
const { brandName } = await this.settingService.getServerBrand();
114118
return {
115119
notifyMessage: message,
116-
subject: `${title} - ${this.baseConfig.brandName}`,
120+
subject: `${title} - ${brandName}`,
117121
template: 'normal',
118122
context: {
119123
partialBody: 'common-body',
124+
brandName,
120125
...info,
121126
},
122127
};
123128
}
124129

125-
resetPasswordEmailOptions(info: { name: string; email: string; resetPasswordUrl: string }) {
130+
async resetPasswordEmailOptions(info: { name: string; email: string; resetPasswordUrl: string }) {
126131
const { name, email, resetPasswordUrl } = info;
132+
const { brandName } = await this.settingService.getServerBrand();
127133
return {
128-
subject: `Reset your password - ${this.baseConfig.brandName}`,
134+
subject: `Reset your password - ${brandName}`,
129135
template: 'normal',
130136
context: {
131137
name,
132138
email,
133139
resetPasswordUrl,
140+
brandName,
134141
partialBody: 'reset-password',
135142
},
136143
};
137144
}
138145

139-
sendEmailVerifyCodeEmailOptions(info: { title: string; message: string }) {
146+
async sendEmailVerifyCodeEmailOptions(info: { title: string; message: string }) {
140147
const { title } = info;
148+
const { brandName } = await this.settingService.getServerBrand();
141149
return {
142-
subject: `${title} - ${this.baseConfig.brandName}`,
150+
subject: `${title} - ${brandName}`,
143151
template: 'normal',
144152
context: {
145153
partialBody: 'email-verify-code',
154+
brandName,
146155
...info,
147156
},
148157
};

apps/nestjs-backend/src/features/mail-sender/templates/pages/normal.hbs

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6-
<title>Teable Email</title>
76
<style type="text/css">
87
/* Client-specific styles */
98
#outlook a {

apps/nestjs-backend/src/features/notification/notification.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Module } from '@nestjs/common';
22
import { ShareDbModule } from '../../share-db/share-db.module';
3+
import { MailSenderModule } from '../mail-sender/mail-sender.module';
34
import { UserModule } from '../user/user.module';
45
import { NotificationController } from './notification.controller';
56
import { NotificationService } from './notification.service';
67

78
@Module({
8-
imports: [ShareDbModule, UserModule],
9+
imports: [ShareDbModule, UserModule, MailSenderModule.register()],
910
controllers: [NotificationController],
1011
providers: [NotificationService],
1112
exports: [NotificationService],

0 commit comments

Comments
 (0)