Initial Mento CRM: portal OTP, reports, SMS, and deploy for crm.montzar.ir.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 21:06:30 +03:30
co-authored by Cursor
commit 29a040c21d
195 changed files with 39430 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
import {
ConflictException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcryptjs';
import type { AuthUserDto, LoginResponse } from '@mento/shared';
import { PrismaService } from '../prisma/prisma.service';
import { LoginDto, RegisterOrganizationDto } from './dto';
function slugify(input: string): string {
return input
.trim()
.toLowerCase()
.replace(/[^\w\u0600-\u06FF]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 48) || `org-${Date.now()}`;
}
@Injectable()
export class AuthService {
constructor(
private readonly prisma: PrismaService,
private readonly jwt: JwtService,
) {}
async registerOrganization(dto: RegisterOrganizationDto): Promise<LoginResponse> {
const baseSlug = slugify(dto.organizationName);
let slug = baseSlug;
let i = 1;
while (await this.prisma.organization.findUnique({ where: { slug } })) {
slug = `${baseSlug}-${i++}`;
}
const existing = await this.prisma.user.findFirst({
where: { email: dto.email.toLowerCase() },
});
if (existing) {
throw new ConflictException('این ایمیل قبلاً ثبت شده است');
}
const passwordHash = await bcrypt.hash(dto.password, 10);
const organization = await this.prisma.organization.create({
data: {
name: dto.organizationName,
slug,
users: {
create: {
email: dto.email.toLowerCase(),
fullName: dto.fullName,
passwordHash,
role: 'owner',
},
},
},
include: { users: true },
});
const user = organization.users[0];
return this.buildLoginResponse(user);
}
async login(dto: LoginDto): Promise<LoginResponse> {
const user = await this.prisma.user.findFirst({
where: { email: dto.email.toLowerCase() },
});
if (!user) {
throw new UnauthorizedException('ایمیل یا رمز عبور نادرست است');
}
const ok = await bcrypt.compare(dto.password, user.passwordHash);
if (!ok) {
throw new UnauthorizedException('ایمیل یا رمز عبور نادرست است');
}
return this.buildLoginResponse(user);
}
async me(userId: string): Promise<AuthUserDto> {
const user = await this.prisma.user.findUnique({ where: { id: userId } });
if (!user) {
throw new UnauthorizedException();
}
return this.toAuthUser(user);
}
private buildLoginResponse(user: {
id: string;
email: string;
fullName: string;
role: AuthUserDto['role'];
organizationId: string;
}): LoginResponse {
const payload = {
sub: user.id,
organizationId: user.organizationId,
role: user.role,
};
return {
accessToken: this.jwt.sign(payload),
user: this.toAuthUser(user),
};
}
private toAuthUser(user: {
id: string;
email: string;
fullName: string;
role: AuthUserDto['role'];
organizationId: string;
}): AuthUserDto {
return {
id: user.id,
email: user.email,
fullName: user.fullName,
role: user.role,
organizationId: user.organizationId,
};
}
}