chore: migrate to pnpm workspaces monorepo with Turborepo
- Restructure directories: apps/api, apps/admin, apps/website - Add root pnpm-workspace.yaml, turbo.json, .prettierrc, .gitignore - Rename packages to @inkreach/api, @inkreach/admin, @inkreach/website - Add shared packages: packages/tsconfig, packages/shared-types - Add pnpm.onlyBuiltDependencies for native builds - Update docs: README.md, structs.md - All three projects build successfully
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
ConflictException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
import { RegisterDto } from './dto/register.dto';
|
||||
import type { JwtPayload } from './strategies/jwt.strategy';
|
||||
|
||||
export interface PublicUser {
|
||||
id: string;
|
||||
username: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface LoginResult {
|
||||
accessToken: string;
|
||||
user: PublicUser;
|
||||
}
|
||||
|
||||
const BCRYPT_ROUNDS = 10;
|
||||
const TOKEN_EXPIRES_IN = '7d';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly jwt: JwtService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Registers a brand-new admin user. Throws {@link ConflictException}
|
||||
* if the username is already taken.
|
||||
*/
|
||||
async register(dto: RegisterDto): Promise<PublicUser> {
|
||||
const existing = await this.prisma.user.findUnique({
|
||||
where: { username: dto.username },
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException('Username already exists');
|
||||
}
|
||||
const passwordHash = await bcrypt.hash(dto.password, BCRYPT_ROUNDS);
|
||||
const created = await this.prisma.user.create({
|
||||
data: { username: dto.username, passwordHash },
|
||||
});
|
||||
return this.toPublic(created);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies credentials and returns a signed JWT.
|
||||
*/
|
||||
async login(dto: LoginDto): Promise<LoginResult> {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { username: dto.username },
|
||||
});
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
const ok = await bcrypt.compare(dto.password, user.passwordHash);
|
||||
if (!ok) {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
const payload: JwtPayload = {
|
||||
sub: user.id.toString(),
|
||||
username: user.username,
|
||||
};
|
||||
const accessToken = await this.jwt.signAsync(payload, {
|
||||
expiresIn: TOKEN_EXPIRES_IN,
|
||||
});
|
||||
return { accessToken, user: this.toPublic(user) };
|
||||
}
|
||||
|
||||
private toPublic(user: {
|
||||
id: bigint;
|
||||
username: string;
|
||||
createdAt: Date;
|
||||
}): PublicUser {
|
||||
return {
|
||||
id: user.id.toString(),
|
||||
username: user.username,
|
||||
createdAt: user.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user