Initial commit: Meshkee CMS API

NestJS backend with Prisma, Docker Compose for Postgres/Redis, and deploy docs for the production VM.
This commit is contained in:
Ali Reza
2026-07-21 17:52:36 +03:30
commit bb59d5e9ba
254 changed files with 37031 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
import {
BadRequestException,
ConflictException,
ForbiddenException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { AuthUser } from '../auth/auth.types';
import { PermissionsService } from '../auth/permissions.service';
import { PrismaService } from '../prisma/prisma.service';
import { DisableDomainDto } from './dto/disable-domain.dto';
import { ListDomainsDto } from './dto/list-domains.dto';
import { ToggleSslDto } from './dto/toggle-ssl.dto';
import { UpdateDomainAdminDto } from './dto/update-domain-admin.dto';
type DomainRow = {
id: bigint;
host: string;
businessId: bigint;
businessName: string;
sslEnabled: boolean;
isActive: boolean;
expiresAt: Date | null;
createdAt: Date;
};
@Injectable()
export class DomainAdminService {
constructor(
private readonly prisma: PrismaService,
private readonly permissions: PermissionsService,
) {}
private async assertSuperAdmin(actor: AuthUser) {
if (!(await this.permissions.isSuperAdmin(actor.id))) {
throw new ForbiddenException('Super admin access required');
}
}
async list(query: ListDomainsDto, actor: AuthUser) {
await this.assertSuperAdmin(actor);
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 10;
const skip = (page - 1) * pageSize;
const nameLike = query.name?.trim() ? `%${query.name.trim()}%` : null;
const where = Prisma.sql`
WHERE 1=1
${nameLike ? Prisma.sql`AND d.host ILIKE ${nameLike}` : Prisma.empty}
`;
const [items, totalRow] = await Promise.all([
this.prisma.$queryRaw<DomainRow[]>(Prisma.sql`
SELECT
d.id AS "id",
d.host AS "host",
d.business_id AS "businessId",
b.name AS "businessName",
d.ssl_enabled AS "sslEnabled",
d.is_active AS "isActive",
d.expires_at AS "expiresAt",
d.created_at AS "createdAt"
FROM domains d
JOIN businesses b ON b.id = d.business_id
${where}
ORDER BY d.created_at DESC
LIMIT ${pageSize} OFFSET ${skip}
`),
this.prisma.$queryRaw<{ total: number }[]>(Prisma.sql`
SELECT COUNT(*)::int AS "total"
FROM domains d
${where}
`),
]);
return { items, total: totalRow[0]?.total ?? 0, page, pageSize };
}
async update(domainIdRaw: string, dto: UpdateDomainAdminDto, actor: AuthUser) {
await this.assertSuperAdmin(actor);
const domainId = BigInt(domainIdRaw);
const domain = await this.prisma.domain.findUnique({ where: { id: domainId } });
if (!domain) {
throw new NotFoundException('Domain not found');
}
if (dto.host) {
const host = dto.host.trim();
const existing = await this.prisma.domain.findUnique({ where: { host } });
if (existing && existing.id !== domainId) {
throw new ConflictException('Domain host is already taken');
}
}
return this.prisma.domain.update({
where: { id: domainId },
data: {
host: dto.host?.trim(),
expiresAt: dto.expiresAt !== undefined ? new Date(dto.expiresAt) : undefined,
},
});
}
async disable(domainIdRaw: string, dto: DisableDomainDto, actor: AuthUser) {
await this.assertSuperAdmin(actor);
const domainId = BigInt(domainIdRaw);
const domain = await this.prisma.domain.findUnique({ where: { id: domainId } });
if (!domain) {
throw new NotFoundException('Domain not found');
}
return this.prisma.domain.update({
where: { id: domainId },
data: { isActive: dto.isActive },
});
}
async toggleSsl(domainIdRaw: string, dto: ToggleSslDto, actor: AuthUser) {
await this.assertSuperAdmin(actor);
const domainId = BigInt(domainIdRaw);
const domain = await this.prisma.domain.findUnique({ where: { id: domainId } });
if (!domain) {
throw new NotFoundException('Domain not found');
}
return this.prisma.domain.update({
where: { id: domainId },
data: { sslEnabled: dto.sslEnabled },
});
}
async remove(domainIdRaw: string, actor: AuthUser) {
await this.assertSuperAdmin(actor);
const domainId = BigInt(domainIdRaw);
const domain = await this.prisma.domain.findUnique({ where: { id: domainId } });
if (!domain) {
throw new NotFoundException('Domain not found');
}
await this.prisma.domain.delete({ where: { id: domainId } });
return { message: 'Domain removed' };
}
}