mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-27 19:15:12 +02:00
feat: Backend typing and openapi schema & codegen
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UserController } from './user.controller';
|
||||
|
||||
describe('UserController', () => {
|
||||
let controller: UserController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [UserController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<UserController>(UserController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -8,55 +8,65 @@ import {
|
||||
Param,
|
||||
Post,
|
||||
UseGuards,
|
||||
Request,
|
||||
} from '@nestjs/common';
|
||||
import { UserService } from './user.service';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
import { AuthUser } from 'src/auth/auth.service';
|
||||
import { AuthGuard, GetUser } from '../auth/auth.guard';
|
||||
import { ApiNotFoundResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { CreateUserDto, UserDto } from './user.dto';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@ApiTags('user')
|
||||
@Controller('user')
|
||||
export class UserController {
|
||||
constructor(private userService: UserService) {}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Get()
|
||||
async getProfile(@Request() req) {
|
||||
const user = await this.userService.findOne((req.user as AuthUser).id);
|
||||
|
||||
@ApiNotFoundResponse({ description: 'User not found' })
|
||||
@ApiOkResponse({ description: 'User found', type: UserDto })
|
||||
async getProfile(@GetUser() user: User): Promise<UserDto> {
|
||||
if (!user) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return user;
|
||||
return UserDto.fromEntity(user);
|
||||
}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@Get(':id')
|
||||
async findById(@Param('id') id: string) {
|
||||
@ApiOkResponse({ description: 'User found', type: UserDto })
|
||||
@ApiNotFoundResponse({ description: 'User not found' })
|
||||
async findById(
|
||||
@Param('id') id: string,
|
||||
@GetUser() callerUser: User,
|
||||
): Promise<UserDto> {
|
||||
if (!callerUser.isAdmin && callerUser.id !== id) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
const user = await this.userService.findOne(id);
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return user;
|
||||
return UserDto.fromEntity(user);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post()
|
||||
async create(
|
||||
@Body()
|
||||
userCreateDto: {
|
||||
name: string;
|
||||
password: string;
|
||||
isAdmin?: boolean;
|
||||
},
|
||||
userCreateDto: CreateUserDto,
|
||||
) {
|
||||
const canCreateAdmin = await this.userService.noPreviousAdmins();
|
||||
|
||||
return this.userService.create(
|
||||
const user = await this.userService.create(
|
||||
userCreateDto.name,
|
||||
userCreateDto.password,
|
||||
canCreateAdmin && userCreateDto.isAdmin,
|
||||
);
|
||||
|
||||
return UserDto.fromEntity(user);
|
||||
}
|
||||
}
|
||||
|
||||
21
backend/src/user/user.dto.ts
Normal file
21
backend/src/user/user.dto.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { OmitType, PickType } from '@nestjs/swagger';
|
||||
import { User } from './user.entity';
|
||||
|
||||
export class UserDto extends OmitType(User, ['password'] as const) {
|
||||
static fromEntity(entity: User): UserDto {
|
||||
return {
|
||||
id: entity.id,
|
||||
name: entity.name,
|
||||
isAdmin: entity.isAdmin,
|
||||
settings: entity.settings,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CreateUserDto extends PickType(User, [
|
||||
'name',
|
||||
'password',
|
||||
'isAdmin',
|
||||
] as const) {}
|
||||
|
||||
export class UpdateUserDto extends OmitType(User, ['id'] as const) {}
|
||||
@@ -1,6 +1,60 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
export class SonarrSettings {
|
||||
@ApiProperty({ required: true })
|
||||
apiKey: string;
|
||||
@ApiProperty({ required: true })
|
||||
baseUrl: string;
|
||||
@ApiProperty({ required: true })
|
||||
qualityProfileId: number;
|
||||
@ApiProperty({ required: true })
|
||||
rootFolderPath: string;
|
||||
@ApiProperty({ required: true })
|
||||
languageProfileId: number;
|
||||
}
|
||||
|
||||
export class RadarrSettings {
|
||||
@ApiProperty({ required: true })
|
||||
apiKey: string;
|
||||
@ApiProperty({ required: true })
|
||||
baseUrl: string;
|
||||
@ApiProperty({ required: true })
|
||||
qualityProfileId: number;
|
||||
@ApiProperty({ required: true })
|
||||
rootFolderPath: string;
|
||||
}
|
||||
|
||||
export class JellyfinSettings {
|
||||
@ApiProperty({ required: true })
|
||||
apiKey: string;
|
||||
@ApiProperty({ required: true })
|
||||
baseUrl: string;
|
||||
@ApiProperty({ required: true })
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export class Settings {
|
||||
@ApiProperty({ required: true })
|
||||
autoplayTrailers: boolean;
|
||||
@ApiProperty({ required: true })
|
||||
language: string;
|
||||
@ApiProperty({ required: true })
|
||||
animationDuration: number;
|
||||
// discover: {
|
||||
// region: string,
|
||||
// excludeLibraryItems: true,
|
||||
// includedLanguages: 'en'
|
||||
// },
|
||||
@ApiProperty({ required: true, type: SonarrSettings })
|
||||
sonarr: SonarrSettings;
|
||||
@ApiProperty({ required: true, type: RadarrSettings })
|
||||
radarr: RadarrSettings;
|
||||
@ApiProperty({ required: true, type: JellyfinSettings })
|
||||
jellyfin: JellyfinSettings;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: Settings = {
|
||||
autoplayTrailers: true,
|
||||
language: 'en',
|
||||
animationDuration: 300,
|
||||
@@ -31,45 +85,23 @@ const DEFAULT_SETTINGS = {
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@ApiProperty({ required: true })
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
@Column({ unique: true })
|
||||
name: string;
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
@Column()
|
||||
password: string;
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
@Column()
|
||||
isAdmin: boolean = false;
|
||||
|
||||
@ApiProperty({ required: true, type: Settings })
|
||||
@Column('json', { default: JSON.stringify(DEFAULT_SETTINGS) })
|
||||
settings: {
|
||||
autoplayTrailers: boolean;
|
||||
language: string;
|
||||
animationDuration: number;
|
||||
// discover: {
|
||||
// region: string,
|
||||
// excludeLibraryItems: true,
|
||||
// includedLanguages: 'en'
|
||||
// },
|
||||
sonarr: {
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
qualityProfileId: number;
|
||||
rootFolderPath: string;
|
||||
languageProfileId: number;
|
||||
};
|
||||
radarr: {
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
qualityProfileId: number;
|
||||
rootFolderPath: string;
|
||||
};
|
||||
jellyfin: {
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
userId: string;
|
||||
};
|
||||
} = DEFAULT_SETTINGS;
|
||||
settings = DEFAULT_SETTINGS;
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
describe('UserService', () => {
|
||||
let service: UserService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [UserService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UserService>(UserService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user