mirror of
https://github.com/PeterMaquiran/tvone-api.git
synced 2026-04-18 16:27:51 +00:00
add moodules
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import { IsOptional, IsString, MaxLength } from 'class-validator';
|
||||
|
||||
export class UpdateMeDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
displayName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(512)
|
||||
avatarKey?: string;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { KeycloakRequestUser } from '../../shared/decorators/current-user.decorator';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
@Injectable()
|
||||
export class UserProvisioningGuard implements CanActivate {
|
||||
constructor(private readonly usersService: UsersService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
const keycloakUser = req.user as KeycloakRequestUser | undefined;
|
||||
if (!keycloakUser?.userId) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
req.dbUser = await this.usersService.ensureUser(keycloakUser);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Body, Controller, Get, Patch, UseGuards } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { CurrentDbUser } from '../../shared/decorators/current-db-user.decorator';
|
||||
import { User } from '@prisma/client';
|
||||
import { UpdateMeDto } from './dto/update-me.dto';
|
||||
import { UsersService } from './users.service';
|
||||
import { UserProvisioningGuard } from './user-provisioning.guard';
|
||||
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
constructor(private readonly usersService: UsersService) {}
|
||||
|
||||
@UseGuards(AuthGuard('keycloak'), UserProvisioningGuard)
|
||||
@Get('me')
|
||||
me(@CurrentDbUser() user: User) {
|
||||
return user;
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard('keycloak'), UserProvisioningGuard)
|
||||
@Patch('me')
|
||||
updateMe(@CurrentDbUser() user: User, @Body() dto: UpdateMeDto) {
|
||||
return this.usersService.updateProfile(user.id, dto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { UserProvisioningGuard } from './user-provisioning.guard';
|
||||
import { UsersController } from './users.controller';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PassportModule.register({ defaultStrategy: 'keycloak' }),
|
||||
AuthModule,
|
||||
],
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService, UserProvisioningGuard],
|
||||
exports: [UsersService, UserProvisioningGuard],
|
||||
})
|
||||
export class UsersModule {}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, User, UserRole } from '@prisma/client';
|
||||
import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
import { KeycloakRequestUser } from '../../shared/decorators/current-user.decorator';
|
||||
|
||||
function mapKeycloakRolesToUserRole(realmRoles: string[]): UserRole {
|
||||
const lower = realmRoles.map((r) => r.toLowerCase());
|
||||
if (lower.includes('admin')) return UserRole.ADMIN;
|
||||
if (lower.includes('editor')) return UserRole.EDITOR;
|
||||
if (lower.includes('author')) return UserRole.AUTHOR;
|
||||
return UserRole.READER;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async ensureUser(kc: KeycloakRequestUser): Promise<User> {
|
||||
const email = kc.email ?? `${kc.userId}@placeholder.local`;
|
||||
const role = mapKeycloakRolesToUserRole(kc.roles ?? []);
|
||||
|
||||
const existing = await this.prisma.user.findUnique({
|
||||
where: { keycloakId: kc.userId },
|
||||
});
|
||||
if (existing) {
|
||||
const data: Prisma.UserUpdateInput = {};
|
||||
if (kc.email && kc.email !== existing.email) data.email = kc.email;
|
||||
if (kc.name && kc.name !== existing.displayName) {
|
||||
data.displayName = kc.name;
|
||||
}
|
||||
if (Object.keys(data).length === 0) {
|
||||
return existing;
|
||||
}
|
||||
return this.prisma.user.update({ where: { id: existing.id }, data });
|
||||
}
|
||||
|
||||
return this.prisma.user.create({
|
||||
data: {
|
||||
keycloakId: kc.userId,
|
||||
email,
|
||||
displayName: kc.name ?? null,
|
||||
role,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id: string) {
|
||||
return this.prisma.user.findUnique({ where: { id } });
|
||||
}
|
||||
|
||||
async updateProfile(userId: string, dto: { displayName?: string; avatarKey?: string }) {
|
||||
return this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
displayName: dto.displayName,
|
||||
avatarKey: dto.avatarKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async setRole(userId: string, role: UserRole) {
|
||||
return this.prisma.user.update({ where: { id: userId }, data: { role } });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user