This commit is contained in:
Bastian Wagner
2026-06-10 15:44:18 +02:00
parent 67b5fb8532
commit e1cc78ca27
22 changed files with 749 additions and 26 deletions

View File

@@ -55,6 +55,15 @@ export class AuthController {
return this.authService.getPublicUser(request.user!.sub);
}
@Get('users/search')
@UseGuards(JwtAuthGuard)
searchUsers(
@Req() request: AuthenticatedRequest,
@Query('q') query?: string,
) {
return this.authService.searchUsers(request.user!.sub, query);
}
@Patch('me/onboarding')
@UseGuards(JwtAuthGuard)
updateOnboarding(

View File

@@ -9,7 +9,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from 'crypto';
import { Repository } from 'typeorm';
import { Like, Repository } from 'typeorm';
import { AuditLogService } from '../audit/audit-log.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
@@ -20,6 +20,7 @@ import {
AuthTokens,
JwtTokenPayload,
PublicUser,
PublicUserSearchResult,
} from './auth.types';
import { AppEvents } from '../events/app-events';
import { RefreshTokenEntity } from './refresh-token.entity';
@@ -293,6 +294,41 @@ export class AuthService {
return this.toPublicUser(user);
}
async searchUsers(
actorUserId: string,
query?: string,
): Promise<PublicUserSearchResult[]> {
const normalizedQuery = query?.trim();
if (!normalizedQuery || normalizedQuery.length < 2) {
return [];
}
const pattern = `%${normalizedQuery}%`;
const users = await this.usersRepository.find({
where: [
{ verified: true, email: Like(pattern) },
{ verified: true, name: Like(pattern) },
],
order: { email: 'ASC' },
take: 10,
});
return users
.filter((user) => user.id !== actorUserId)
.filter(
(user, index, allUsers) =>
allUsers.findIndex((existingUser) => existingUser.id === user.id) ===
index,
)
.slice(0, 10)
.map((user) => ({
id: user.id,
email: user.email,
name: user.name ?? undefined,
}));
}
async updateOnboardingCompleted(
userId: string,
completed: boolean,

View File

@@ -37,3 +37,9 @@ export interface PublicUser {
verified: boolean;
onboardingCompleted: boolean;
}
export interface PublicUserSearchResult {
id: string;
email: string;
name?: string;
}

View File

@@ -9,6 +9,7 @@ import {
} from 'typeorm';
import { ListTemplateEntity } from '../list-templates/list-template.entity';
import { UserListEntity } from '../lists/user-list.entity';
import { UserListShareEntity } from '../lists/user-list-share.entity';
import { RefreshTokenEntity } from './refresh-token.entity';
@Entity('users')
@@ -59,4 +60,7 @@ export class UserEntity {
@OneToMany(() => UserListEntity, (list) => list.owner)
lists?: UserListEntity[];
@OneToMany(() => UserListShareEntity, (share) => share.user)
sharedLists?: UserListShareEntity[];
}