Files
CRM_Montzar/apps/api/src/auth/auth.service.ts
T
MahdiandCursor d2f66d11fc Ask for organization business field at signup, including jewelry.
Store Didar-style industry on the org so registration, settings, and Ask Mento share the same catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 16:45:59 +03:30

123 lines
3.2 KiB
TypeScript

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,
businessField: dto.businessField,
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,
};
}
}