improve app registration and component service ports
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-09 14:26:49 +09:00
parent c46c176ca8
commit 3a79364905
5 changed files with 133 additions and 6 deletions
+84 -2
View File
@@ -1,8 +1,13 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { randomBytes, scrypt as scryptCallback } from 'node:crypto';
import { promisify } from 'node:util';
import { AuthPrismaService } from '../auth-prisma/auth-prisma.service';
import { ListUserSessionsQuery } from './dto';
import { CreateManagedUserDto, ListUserSessionsQuery } from './dto';
const scrypt = promisify(scryptCallback);
const passwordKeyLength = 64;
type UserSessionRow = {
id: string;
@@ -144,6 +149,66 @@ export class UsersService {
};
}
async createManagedUser(body: CreateManagedUserDto) {
const email = body.email.trim().toLowerCase();
const existing = await this.authPrisma.user.findUnique({
where: { email },
select: { id: true },
});
if (existing) {
throw new ConflictException('이미 등록된 이메일입니다.');
}
const license = await this.authPrisma.license.findUnique({
where: { code: body.licenseCode ?? 'EDU' },
select: { id: true, defaultDurationDays: true },
});
if (!license) {
throw new NotFoundException('라이센스를 찾을 수 없습니다.');
}
const startsAt = new Date();
const expiresAt = calculateLicenseExpiresAt(
startsAt,
license.defaultDurationDays,
);
const user = await this.authPrisma.user.create({
data: {
email,
passwordHash: await hashPassword(body.password),
name: body.name.trim(),
role: 'USER',
affiliations: body.organizationId
? {
create: {
organizationId: body.organizationId,
department: body.department?.trim() || null,
position: body.position?.trim() || null,
isPrimary: true,
},
}
: undefined,
userLicenses: {
create: {
licenseId: license.id,
startsAt,
expiresAt,
},
},
},
select: {
id: true,
email: true,
name: true,
},
});
return { user };
}
async lockUser(userId: string) {
const user = await this.authPrisma.user.findUnique({
where: { id: userId },
@@ -209,6 +274,23 @@ export class UsersService {
}
}
async function hashPassword(password: string) {
const salt = randomBytes(16).toString('hex');
const derivedKey = (await scrypt(password, salt, passwordKeyLength)) as Buffer;
return `scrypt:${salt}:${derivedKey.toString('hex')}`;
}
function calculateLicenseExpiresAt(
startsAt: Date,
durationDays: number | null | undefined,
) {
if (!durationDays) return null;
const expiresAt = new Date(startsAt);
expiresAt.setDate(expiresAt.getDate() + durationDays);
return expiresAt;
}
function getUserAvatarUrl(
userId: string,
mimeType?: string | null,