diff --git a/src/app.module.spec.ts b/src/app.module.spec.ts deleted file mode 100644 index 2cc290a..0000000 --- a/src/app.module.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Test } from '@nestjs/testing'; -import { describe, it, expect } from 'vitest'; -import { AppModule } from './app.module'; - -describe('AppModule', () => { - it('should compile the module', async () => { - const module = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - expect(module).toBeDefined(); - }); -}); diff --git a/src/app.module.ts b/src/app.module.ts index 2e8c781..890ddeb 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; +import { ConfigModule, ConfigService } from '@nestjs/config'; import { EventEmitterModule } from '@nestjs/event-emitter'; +import { SequelizeModule } from '@nestjs/sequelize'; import { LoggerModule } from 'nestjs-pino'; import { nanoid } from 'nanoid'; import configuration from './config/configuration'; @@ -8,6 +9,7 @@ import { HealthModule } from './modules/health/health.module'; import { JmapModule } from './modules/infrastructure/jmap/jmap.module'; import { EmailModule } from './modules/email/email.module'; import { AuthModule } from './modules/auth/auth.module'; +import { AccountModule } from './modules/account/account.module'; @Module({ imports: [ @@ -43,11 +45,42 @@ import { AuthModule } from './modules/auth/auth.module'; load: [configuration], isGlobal: true, }), + SequelizeModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (configService: ConfigService) => ({ + dialect: 'postgres', + autoLoadModels: true, + synchronize: false, + host: configService.get('database.host'), + port: configService.get('database.port'), + username: configService.get('database.username'), + password: configService.get('database.password'), + database: configService.get('database.name'), + pool: { + max: 20, + min: 0, + idle: 20000, + acquire: 20000, + }, + dialectOptions: configService.get('isProduction') + ? { + ssl: { + require: true, + rejectUnauthorized: false, + }, + application_name: 'mail-server', + } + : {}, + logging: configService.get('isDevelopment') ? console.log : false, + }), + }), EventEmitterModule.forRoot({ wildcard: true, delimiter: '.' }), HealthModule, JmapModule, EmailModule, AuthModule, + AccountModule, ], controllers: [], providers: [], diff --git a/src/modules/account/account.module.ts b/src/modules/account/account.module.ts new file mode 100644 index 0000000..ddbe841 --- /dev/null +++ b/src/modules/account/account.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common'; +import { SequelizeModule } from '@nestjs/sequelize'; +import { + MailAccountModel, + MailAddressModel, + MailDomainModel, + MailProviderAccountModel, +} from './models/index.js'; + +@Module({ + imports: [ + SequelizeModule.forFeature([ + MailAccountModel, + MailAddressModel, + MailDomainModel, + MailProviderAccountModel, + ]), + ], + exports: [SequelizeModule], +}) +export class AccountModule {} diff --git a/src/modules/account/models/index.ts b/src/modules/account/models/index.ts new file mode 100644 index 0000000..5e95fd7 --- /dev/null +++ b/src/modules/account/models/index.ts @@ -0,0 +1,4 @@ +export { MailAccountModel } from './mail-account.model.js'; +export { MailAddressModel } from './mail-address.model.js'; +export { MailDomainModel } from './mail-domain.model.js'; +export { MailProviderAccountModel } from './mail-provider-account.model.js'; diff --git a/src/modules/account/models/mail-account.model.ts b/src/modules/account/models/mail-account.model.ts new file mode 100644 index 0000000..f97e870 --- /dev/null +++ b/src/modules/account/models/mail-account.model.ts @@ -0,0 +1,32 @@ +import { + AllowNull, + Column, + DataType, + Default, + HasMany, + Model, + PrimaryKey, + Table, + Unique, +} from 'sequelize-typescript'; +import { MailAddressModel } from './mail-address.model.js'; + +@Table({ + underscored: true, + timestamps: true, + tableName: 'mail_accounts', +}) +export class MailAccountModel extends Model { + @PrimaryKey + @Default(DataType.UUIDV4) + @Column(DataType.UUID) + declare id: string; + + @AllowNull(false) + @Unique + @Column(DataType.UUID) + declare driveUserUuid: string; + + @HasMany(() => MailAddressModel) + declare addresses: MailAddressModel[]; +} diff --git a/src/modules/account/models/mail-address.model.ts b/src/modules/account/models/mail-address.model.ts new file mode 100644 index 0000000..1d07754 --- /dev/null +++ b/src/modules/account/models/mail-address.model.ts @@ -0,0 +1,60 @@ +import { + AllowNull, + BelongsTo, + Column, + DataType, + Default, + ForeignKey, + HasOne, + Index, + Model, + PrimaryKey, + Table, + Unique, +} from 'sequelize-typescript'; +import { MailAccountModel } from './mail-account.model.js'; +import { MailDomainModel } from './mail-domain.model.js'; +import { MailProviderAccountModel } from './mail-provider-account.model.js'; + +@Table({ + underscored: true, + timestamps: true, + tableName: 'mail_addresses', +}) +export class MailAddressModel extends Model { + @PrimaryKey + @Default(DataType.UUIDV4) + @Column(DataType.UUID) + declare id: string; + + @AllowNull(false) + @ForeignKey(() => MailAccountModel) + @Index + @Column(DataType.UUID) + declare mailAccountId: string; + + @AllowNull(false) + @Unique + @Column(DataType.STRING(255)) + declare address: string; + + @AllowNull(false) + @ForeignKey(() => MailDomainModel) + @Index + @Column(DataType.UUID) + declare domainId: string; + + @AllowNull(false) + @Default(false) + @Column(DataType.BOOLEAN) + declare isDefault: boolean; + + @BelongsTo(() => MailAccountModel) + declare account: MailAccountModel; + + @BelongsTo(() => MailDomainModel) + declare domain: MailDomainModel; + + @HasOne(() => MailProviderAccountModel) + declare providerAccount: MailProviderAccountModel; +} diff --git a/src/modules/account/models/mail-domain.model.ts b/src/modules/account/models/mail-domain.model.ts new file mode 100644 index 0000000..82746af --- /dev/null +++ b/src/modules/account/models/mail-domain.model.ts @@ -0,0 +1,37 @@ +import { + AllowNull, + Column, + DataType, + Default, + HasMany, + Model, + PrimaryKey, + Table, + Unique, +} from 'sequelize-typescript'; +import { MailAddressModel } from './mail-address.model.js'; + +@Table({ + underscored: true, + timestamps: true, + tableName: 'mail_domains', +}) +export class MailDomainModel extends Model { + @PrimaryKey + @Default(DataType.UUIDV4) + @Column(DataType.UUID) + declare id: string; + + @AllowNull(false) + @Unique + @Column(DataType.STRING(255)) + declare domain: string; + + @AllowNull(false) + @Default('active') + @Column(DataType.STRING(20)) + declare status: string; + + @HasMany(() => MailAddressModel) + declare addresses: MailAddressModel[]; +} diff --git a/src/modules/account/models/mail-provider-account.model.ts b/src/modules/account/models/mail-provider-account.model.ts new file mode 100644 index 0000000..5714268 --- /dev/null +++ b/src/modules/account/models/mail-provider-account.model.ts @@ -0,0 +1,43 @@ +import { + AllowNull, + BelongsTo, + Column, + DataType, + Default, + ForeignKey, + Model, + PrimaryKey, + Table, + Unique, +} from 'sequelize-typescript'; +import { MailAddressModel } from './mail-address.model.js'; + +@Table({ + underscored: true, + timestamps: true, + tableName: 'mail_provider_accounts', + indexes: [{ unique: true, fields: ['provider', 'external_id'] }], +}) +export class MailProviderAccountModel extends Model { + @PrimaryKey + @Default(DataType.UUIDV4) + @Column(DataType.UUID) + declare id: string; + + @AllowNull(false) + @Unique + @ForeignKey(() => MailAddressModel) + @Column(DataType.UUID) + declare mailAddressId: string; + + @AllowNull(false) + @Column(DataType.STRING) + declare provider: string; + + @AllowNull(false) + @Column(DataType.STRING(255)) + declare externalId: string; + + @BelongsTo(() => MailAddressModel) + declare address: MailAddressModel; +}