Make Arvan DNS updates opt-in and persist English user names.
Domain add/edit only upserts Arvan records when updateDns is true, and super-admin user PATCH now accepts firstNameEn and lastNameEn. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
f61bba317e
commit
8609d8d4fd
@@ -548,7 +548,7 @@ export class BusinessAdminService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const dnsError = await this.applyArvanDns(host);
|
const dnsError = dto.updateDns ? await this.applyArvanDns(host) : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...domain,
|
...domain,
|
||||||
@@ -648,7 +648,7 @@ export class BusinessAdminService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const dnsError = await this.applyArvanDns(host);
|
const dnsError = dto.updateDns ? await this.applyArvanDns(host) : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...updated,
|
...updated,
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ export class AddDomainDto {
|
|||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
isPrimary?: boolean;
|
isPrimary?: boolean;
|
||||||
|
|
||||||
|
/** When true, upsert Meshkee tenant records in Arvan DNS. */
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
updateDns?: boolean;
|
||||||
|
|
||||||
/** HTTPS or SSH git URL — when set, provisions storefront deploy on the websites VM. */
|
/** HTTPS or SSH git URL — when set, provisions storefront deploy on the websites VM. */
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IsOptional, IsString, Matches, MinLength } from 'class-validator';
|
import { IsBoolean, IsOptional, IsString, Matches, MinLength } from 'class-validator';
|
||||||
import { GIT_REPO_URL_RE } from '../../website-deploy/website-deploy.util';
|
import { GIT_REPO_URL_RE } from '../../website-deploy/website-deploy.util';
|
||||||
|
|
||||||
export class UpdateDomainDto {
|
export class UpdateDomainDto {
|
||||||
@@ -6,6 +6,11 @@ export class UpdateDomainDto {
|
|||||||
@MinLength(1)
|
@MinLength(1)
|
||||||
host!: string;
|
host!: string;
|
||||||
|
|
||||||
|
/** When true, upsert Meshkee tenant records in Arvan DNS. */
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
updateDns?: boolean;
|
||||||
|
|
||||||
/** HTTPS or SSH git URL — when set, provisions storefront deploy on the websites VM. */
|
/** HTTPS or SSH git URL — when set, provisions storefront deploy on the websites VM. */
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
import { ArvanDnsModule } from '../arvan-dns/arvan-dns.module';
|
||||||
import { WebsiteDeployModule } from '../website-deploy/website-deploy.module';
|
import { WebsiteDeployModule } from '../website-deploy/website-deploy.module';
|
||||||
import { DomainAdminController } from './domain-admin.controller';
|
import { DomainAdminController } from './domain-admin.controller';
|
||||||
import { DomainAdminService } from './domain-admin.service';
|
import { DomainAdminService } from './domain-admin.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AuthModule, ConfigModule, WebsiteDeployModule],
|
imports: [AuthModule, ConfigModule, WebsiteDeployModule, ArvanDnsModule],
|
||||||
controllers: [DomainAdminController],
|
controllers: [DomainAdminController],
|
||||||
providers: [DomainAdminService],
|
providers: [DomainAdminService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import {
|
|||||||
BadRequestException,
|
BadRequestException,
|
||||||
ConflictException,
|
ConflictException,
|
||||||
ForbiddenException,
|
ForbiddenException,
|
||||||
|
HttpException,
|
||||||
Injectable,
|
Injectable,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
ServiceUnavailableException,
|
ServiceUnavailableException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
import { ArvanDnsService } from '../arvan-dns/arvan-dns.service';
|
||||||
import { AuthUser } from '../auth/auth.types';
|
import { AuthUser } from '../auth/auth.types';
|
||||||
import { PermissionsService } from '../auth/permissions.service';
|
import { PermissionsService } from '../auth/permissions.service';
|
||||||
import { probeTlsHost } from '../common/tls-probe';
|
import { probeTlsHost } from '../common/tls-probe';
|
||||||
@@ -39,6 +41,7 @@ export class DomainAdminService {
|
|||||||
private readonly permissions: PermissionsService,
|
private readonly permissions: PermissionsService,
|
||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
private readonly websiteDeployAgent: WebsiteDeployAgentService,
|
private readonly websiteDeployAgent: WebsiteDeployAgentService,
|
||||||
|
private readonly arvanDns: ArvanDnsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private async assertSuperAdmin(actor: AuthUser) {
|
private async assertSuperAdmin(actor: AuthUser) {
|
||||||
@@ -478,13 +481,41 @@ export class DomainAdminService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.prisma.domain.update({
|
const updated = await this.prisma.domain.update({
|
||||||
where: { id: domainId },
|
where: { id: domainId },
|
||||||
data: {
|
data: {
|
||||||
host: dto.host?.trim(),
|
host: dto.host?.trim(),
|
||||||
expiresAt: dto.expiresAt !== undefined ? new Date(dto.expiresAt) : undefined,
|
expiresAt: dto.expiresAt !== undefined ? new Date(dto.expiresAt) : undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const host = updated.host;
|
||||||
|
const dnsError = dto.updateDns ? await this.applyArvanDns(host) : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...updated,
|
||||||
|
dnsError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async applyArvanDns(host: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
await this.arvanDns.ensureTenantRecords(host);
|
||||||
|
return null;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof HttpException) {
|
||||||
|
const res = err.getResponse();
|
||||||
|
if (typeof res === 'string') return res;
|
||||||
|
if (res && typeof res === 'object' && 'message' in res) {
|
||||||
|
const message = (res as { message: unknown }).message;
|
||||||
|
return Array.isArray(message) ? message.map(String).join(', ') : String(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (err && typeof err === 'object' && 'message' in err && typeof err.message === 'string') {
|
||||||
|
return err.message;
|
||||||
|
}
|
||||||
|
return 'Arvan DNS update failed';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async disable(domainIdRaw: string, dto: DisableDomainDto, actor: AuthUser) {
|
async disable(domainIdRaw: string, dto: DisableDomainDto, actor: AuthUser) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IsDateString, IsOptional, IsString, MinLength } from 'class-validator';
|
import { IsBoolean, IsDateString, IsOptional, IsString, MinLength } from 'class-validator';
|
||||||
|
|
||||||
export class UpdateDomainAdminDto {
|
export class UpdateDomainAdminDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -9,4 +9,9 @@ export class UpdateDomainAdminDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsDateString()
|
@IsDateString()
|
||||||
expiresAt?: string;
|
expiresAt?: string;
|
||||||
|
|
||||||
|
/** When true, upsert Meshkee tenant records in Arvan DNS. */
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
updateDns?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,38 @@
|
|||||||
import { IsOptional, IsString, MinLength } from 'class-validator';
|
import {
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
MaxLength,
|
||||||
|
MinLength,
|
||||||
|
ValidateIf,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
export class UpdateUserDto {
|
export class UpdateUserDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@MinLength(1)
|
@MinLength(1)
|
||||||
|
@MaxLength(100)
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@MinLength(1)
|
@MinLength(1)
|
||||||
|
@MaxLength(100)
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@ValidateIf((_, value) => typeof value === 'string' && value.trim().length > 0)
|
||||||
|
@MinLength(2)
|
||||||
|
@MaxLength(100)
|
||||||
|
firstNameEn?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@ValidateIf((_, value) => typeof value === 'string' && value.trim().length > 0)
|
||||||
|
@MinLength(2)
|
||||||
|
@MaxLength(100)
|
||||||
|
lastNameEn?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
email?: string;
|
email?: string;
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ type UserListRow = {
|
|||||||
id: bigint;
|
id: bigint;
|
||||||
firstName: string | null;
|
firstName: string | null;
|
||||||
lastName: string | null;
|
lastName: string | null;
|
||||||
|
firstNameEn: string | null;
|
||||||
|
lastNameEn: string | null;
|
||||||
cellNumber: string;
|
cellNumber: string;
|
||||||
roles: string | null;
|
roles: string | null;
|
||||||
roleSlug: string | null;
|
roleSlug: string | null;
|
||||||
@@ -171,7 +173,10 @@ export class UsersService {
|
|||||||
AND (
|
AND (
|
||||||
u.first_name ILIKE ${nameLike}
|
u.first_name ILIKE ${nameLike}
|
||||||
OR u.last_name ILIKE ${nameLike}
|
OR u.last_name ILIKE ${nameLike}
|
||||||
|
OR u.first_name_en ILIKE ${nameLike}
|
||||||
|
OR u.last_name_en ILIKE ${nameLike}
|
||||||
OR (COALESCE(u.first_name, '') || ' ' || COALESCE(u.last_name, '')) ILIKE ${nameLike}
|
OR (COALESCE(u.first_name, '') || ' ' || COALESCE(u.last_name, '')) ILIKE ${nameLike}
|
||||||
|
OR (COALESCE(u.first_name_en, '') || ' ' || COALESCE(u.last_name_en, '')) ILIKE ${nameLike}
|
||||||
)
|
)
|
||||||
` : Prisma.empty}
|
` : Prisma.empty}
|
||||||
${cellNumberIlikeFilter(query.cellNumber)}
|
${cellNumberIlikeFilter(query.cellNumber)}
|
||||||
@@ -209,6 +214,8 @@ export class UsersService {
|
|||||||
u.id AS "id",
|
u.id AS "id",
|
||||||
u.first_name AS "firstName",
|
u.first_name AS "firstName",
|
||||||
u.last_name AS "lastName",
|
u.last_name AS "lastName",
|
||||||
|
u.first_name_en AS "firstNameEn",
|
||||||
|
u.last_name_en AS "lastNameEn",
|
||||||
u.cell_number AS "cellNumber",
|
u.cell_number AS "cellNumber",
|
||||||
u.created_at AS "createdAt",
|
u.created_at AS "createdAt",
|
||||||
u.is_active AS "isActive",
|
u.is_active AS "isActive",
|
||||||
@@ -464,6 +471,10 @@ export class UsersService {
|
|||||||
data: {
|
data: {
|
||||||
firstName: dto.firstName?.trim(),
|
firstName: dto.firstName?.trim(),
|
||||||
lastName: dto.lastName?.trim(),
|
lastName: dto.lastName?.trim(),
|
||||||
|
firstNameEn:
|
||||||
|
dto.firstNameEn !== undefined ? dto.firstNameEn.trim() || null : undefined,
|
||||||
|
lastNameEn:
|
||||||
|
dto.lastNameEn !== undefined ? dto.lastNameEn.trim() || null : undefined,
|
||||||
email: dto.email !== undefined ? dto.email.trim() || null : undefined,
|
email: dto.email !== undefined ? dto.email.trim() || null : undefined,
|
||||||
cellNumber: dto.cellNumber?.trim(),
|
cellNumber: dto.cellNumber?.trim(),
|
||||||
},
|
},
|
||||||
@@ -473,6 +484,8 @@ export class UsersService {
|
|||||||
id: updated.id,
|
id: updated.id,
|
||||||
firstName: updated.firstName,
|
firstName: updated.firstName,
|
||||||
lastName: updated.lastName,
|
lastName: updated.lastName,
|
||||||
|
firstNameEn: updated.firstNameEn,
|
||||||
|
lastNameEn: updated.lastNameEn,
|
||||||
email: updated.email,
|
email: updated.email,
|
||||||
cellNumber: updated.cellNumber,
|
cellNumber: updated.cellNumber,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user