Store Didar-style industry on the org so registration, settings, and Ask Mento share the same catalog. Co-authored-by: Cursor <cursoragent@cursor.com>
123 lines
3.2 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|